3.1 开始
着色器(Shader)是运行在GPU上的小程序,上一篇中我们学会了如何使用着色器来将顶点数组变成我们想要的图形——三角形,在本篇中我们要作出其它图形,比如矩形;着色器的配置方法,还有图形的颜色的配置,实现一些其他色彩等等。
3.2 矩形
3.2.1 添加顶点
如果只运用上一篇的知识,要绘制一个矩形,可以看作绘制两个相连的三角形。如此一来只要向着色器程序中传入两个三角形——6个顶点数据即可,着色器程序也不需要改动。
GLfloat vertices[] = {
// 第一个三角形
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, 0.5f, 0.0f, // 左上角
// 第二个三角形
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
只要主循环中的绘制函数作些许改动,就能实现一个矩形的绘制。
// 将原来的 3改为 6,绘制 6个顶点的三角形
glDrawArrays(GL_TRIANGLES, 0, 6);
3.2.2 索引方式
绘制矩形要使用 6个顶点有些浪费机能,实际上矩形只有 4个顶点,OpenGL 提供了索引绘制方式,只要给出顶点和绘制顺序(索引)即可。
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
GLuint indices[] = { // 注意索引从 0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
要使用索引方式绘制,除了使用 VBO 和 VAO,还必须使用另一种缓冲对象,索引缓冲对象(Element Buffer Object,EBO)
// 创建 VBO 和 VAO 的代码(解绑之前)
// 创建一个 EBO 对象
GLuint EBO;
glGenBuffers(1, &EBO);
// 把新创建的缓冲绑定到 GL_ELEMENT_ARRAY_BUFFER 目标上,与 VBO 和 VAO 的方式不同
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 将索引数据复制到缓冲中,与 VBO 顶点输入类似
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
在主循环中绘制时,需要将原来的 glDrawArrays()
函数改为 glDrawElements()
函数
// 要绘制的顶点数为 6,索引类型为 int 即 GL_UNSIGNED_INT
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
练习:绘制 2个不同颜色的三角形。源码:https://www.cnblogs.com/darkmorn/p/12258911.html
3.3 GLSL
OpenGL 着色器语言 GLSL(OpenGL Shading Language)是一种类C语言,GLSL 专注与计算机图形学,对向量和矩阵等图形学数据有很好的表现。GLSL 用来配置着色器,而着色器的工作就是将一种输入转换成另一种输出,GLSL 的 in
和 out
关键字就对应了输入数据和输出数据。main()
是程序的入口函数,和 C语言类似。
3.3.1 数据类型
类似 C语言,GLSL 中的基本数据类型有:int
、float
、double
、uint
(对象) 和 bool
。
GLSL 有两种容器类型:向量(Vector)和矩阵(Matrix)。
向量类型 | 含义 |
---|---|
vecn |
包含 n 个 float 分量的默认向量,如 vec1 vec2 vec3 vec4 |
bvecn |
包含 n 个 bool 分量的向量 |
ivecn |
包含 n 个 int 分量的向量 |
uvecn |
包含 n 个 unsigned int 分量的向量 |
dvecn |
包含 n 个 double 分量的向量 |
向量可以包含 1~4 个分量,对应 x
y
z
w
,每个分量都是一种基本数据类型。向量是一种灵活的数据类型,可以用 vector.x
获取值或用 vector.xyx
自由组合。
vec2 vectorA = vec2(0.3f, 0.5f);
// 使用 vector.x 获取第一个分量,xyzw 分别表示第1~4个分量,也可以灵活组合为新的向量
vec3 vectorB = vec3(vectorA.x, vectorA.y, vectorA.x,);
vec3 vectorC = vectorA.xyx; // vectorB 和 vectorC 结果一样:(0.3f, 0.5f, 0.3f)
vec4 vectorD = vec4(vectorC, 0.0f); // vectorD:(0.3f, 0.5f, 0.3f, 0.0f)
// 甚至可以方便的进行运算
vec3 vectorE = vectorA.xxy + vectorC.xyz;// vectorE:(0.6f, 0.8f, 0.8f)
3.3.2 语法
GLSL 总是以版本号开始,语法基本同于 C语言,一个标准的着色器源程序就像下面这样:
#version version_number
// 输入数据
in type in_variable_name0;
in type in_variable_name1;
// 输出数据
out type out_variable_name;
// 全局数据
uniform type uniform_name;
int main(){
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
着色器是独立封装的,它们之间的通信只有靠输入和输出,每个阶段接收上一阶段的输出作为输入,处理之后输出给下一阶段使用。用 in
定义的数据会由程序匹配上一阶段 out
定义的同名同类型的数据。这样就实现了数据的串联。
但是对于整个渲染管线的入口和出口来说,它们与内存数据对接,所以需要作些限制。也就是顶点着色器的输入(顶点数据)和片段着色器的输出(颜色值)是比较特殊的。
- 顶点着色器输入的顶点数据要使用
layout (location = 0) in vec3 position;
来接收,layout (location = 0)
将接收到的数据保存到属性值为 0 的位置上。在 OpenGL 中一共定义了至少 16 个顶点属性,每个能存储一个最多 4分量的向量。 - 片段着色器输出的颜色值变量名可以任意命名,但类型必须是
vec4
除了 in
和 out
外,还有一种数据交换方式为 uniform
,uniform
是 GLSL 的全局变量,可以在任何着色器上定义,一旦定义就可以由任何着色器使用。同时这种变量还可以由 cpu(用户程序)取值和修改。
3.3.3 彩色三角
颜色信息除了由片段着色器处理,也可以直接传给顶点着色器,再由片段着色器原样输出,减少片段着色器的工作量。
GLfloat vertices[] = {
// 位置 // 颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
};
我们在顶点的位置信息后加上了颜色信息,着色器就需要对信息分开处理。使用不同的顶点属性也就是 location
来区分。
#version 330 core
layout (location = 0) in vec3 position; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 color; // 颜色变量的属性位置值为 1
out vec3 ourColor; // 向片段着色器输出一个颜色
void main(){
gl_Position = vec4(position, 1.0);
ourColor = color; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}
片段着色器只需将颜色值加上 alpha 值(不透明度)后直接输出即可。
#version 330 core
in vec3 ourColor;
out vec4 color;
void main(){
color = vec4(ourColor, 1.0f);
}
最后对在链接顶点时使用的 glVertexAttribPointer()
函数稍作修改即可。
// 位置属性,location 值为 0,步长为 6 个 float 的大小,距起始的偏移量为 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 颜色属性,location 值为 1,步长为 6 个 float 的大小,距起始的偏移量为 3 个 float 的大小
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));
glEnableVertexAttribArray(1);
最后的结果就是这样:
我们确定了三个角的颜色,而 OpenGL 会将片段内的其它地方用颜色插值来填色,计算结果相当于平均它周围的颜色。
3.3.4 色彩变化
接下来我们做点不一样的,让三角形的色彩变化起来。
顶点着色器添加一个全局 uniform
向量,对不同的角使用它来进行变化计算。
#version 330 core
uniform vec3 change;
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 ourColor;
void main(){
gl_Position = vec4(position, 1.0f);
if(color.x > 0.0f) ourColor = change.xyz;
if(color.y > 0.0f) ourColor = change.yzx;
if(color.z > 0.0f) ourColor = change.zxy;
//ourColor = color;
}
在主循环里,要用 glfwGetTime()
获取当前运行时间,用 glGetUniformLocation()
函数获取 uniform
向量,对其红绿蓝色进行交替性变化,加上 GLSL 实现三个角色彩在红绿蓝三色渐变的效果。
// 实时改变三角形的颜色
// 主循环中:
glUseProgram(shaderProgram);
GLfloat timeValue = glfwGetTime();
float speedDown = 5;
timeValue = timeValue / speedDown;
GLint intValue = timeValue;
GLfloat xValue = (timeValue - intValue)*3;
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "change");
GLfloat redValue = xValue < 1.0f ? 1.0f - xValue : (xValue >= 2.0f ? xValue - 2.0f : 0.0f);
GLfloat greenValue = xValue < 2.0f && xValue >= 1.0f ? 2.0f - xValue : (xValue < 1.0f ? xValue : 0.0f);
GLfloat blueValue = xValue >= 2.0f ? 3.0f - xValue : (xValue < 2.0f && xValue >= 1.0f ? xValue - 1.0f : 0.0f);
glUniform3f(vertexColorLocation, redValue, greenValue, blueValue);
来源:CSDN
作者:黛˙晨
链接:https://blog.csdn.net/weixin_44078311/article/details/104180980