OpenGL入门——着色器
前面几节简单使用了一下着色器
现在详细解释一下着色器和着色器语言(GLSL)
1. 着色器
着色器是运行在GPU上的小程序,它们之间不能互相通信,唯一的沟通只有输入和输出。
2. GLSL
着色器的开头是声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数里面处理所有的输入变量,将结果输出到输出变量。uniform是一种从CPU向GPU发送数据的方式。但是它和输入变量又不一样,因为它是全局的,每个着色器程序使用的都是同一个uniform值。
#version version_number in type in_variable_name; in type in_variable_name; out type out_variable_name; uniform type uniform_name; int main() { // 处理输入并进行一些图形操作 ... // 输出处理过的结果到输出变量 out_variable_name = weird_stuff_we_processed; }
3. 数据类型
GLSL的基础数据类型包含:int
、float
、double
、uint
和bool
容器类型包含:向量和矩阵
1)向量:vecn(包含n
个float分量)、bvecn(包含n
个bool分量)、ivecn(包含n
个int分量)、dvecn(包含n
个double分量)、uvecn(包含n
个uint分量);
2)矩阵:glm::matn(n维矩阵,现在还没怎么看到,后面看到再更新)
4. 输入和输出
虽然每个着色器是独立的小程序,但它们也是整体的一部分,所以需要用输入和输出进行数据交流和传递。只要输出变量与下一个着色器的输入匹配上了(类型、名称完全一致即可匹配上),它就会传递下去。
注意:
4.1 顶点着色器使用layout指定输入变量,如layout(location=0),这样就可以之间在CPU上配置顶点属性了,如glVertexAttribPointer的第一个参数就是layout标识。
#version 330 core layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 out vec4 vertexColor; // 为片段着色器指定一个颜色输出 void main() { gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数 vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色 }
4.2 片段着色器是要生成对应像素的最终颜色,所以须有一个vec4的颜色输出变量,否则最后物体会被渲染为黑色或白色
#version 330 core out vec4 FragColor; in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同) void main() { FragColor = vertexColor; }
将OpenGL入门——第一个三角形 - 一只小瓶子 - 博客园 (cnblogs.com)中的着色器源码替换为上面两个,那么绘制效果会是一个暗红色的三角形。
5. Uniform
uniform是一种从CPU传向GPU的发送方式,它是全局的,且在每个着色器中都是独一无二的,可以被任意着色器在任意阶段访问。
#version 330 core out vec4 FragColor; uniform vec4 ourColor; // 在程序代码中设定这个变量 void main() { FragColor = ourColor; }
然后在CPU程序中设置这个uniform值
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//找到着色器中uniform属性的索引/位置值,返回-1就代表没有找到这个位置值 glUniform4f(vertexColorLocation, 0.0f, 1.0, 0.0f, 1.0f);//设置uniform值,类型为vec4
将OpenGL入门——第一个三角形 - 一只小瓶子 - 博客园 (cnblogs.com)中的片段着色器源码替换为上面那个,那么绘制效果会是一个绿色的三角形。
如果在绘制循环里动态修改这个uniform值,那么渲染效果就是一个颜色会变化的三角形。
while (!glfwWindowShouldClose(window)) { float timeValue = glfwGetTime(); float greenValue = (sin(timeValue) / 2.0f) + 0.5f; int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//找到着色器中uniform属性的索引/位置值,返回-1就代表没有找到这个位置值 processInput(window); //清空屏幕 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ///绘制物体 glUseProgram(shaderProgram);//激活程序对象 glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);//设置uniform值,必须在激活程序对象之后才能更新 glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3);//使用VAO绘制:绘制图元为三角形,起始索引0,绘制顶点数量3 //glBindVertexArray(0);//只绘制一个物体,不需要重复绑定和解绑VAO glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲) glfwPollEvents();//检查有没有触发什么事件 }
注意:更新一个uniform之前必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。
6. 更多属性
上面4.1说到顶点着色器使用layout指定顶点属性位置,注意就可以在CPU中设置顶点属性
顶点着色器
//vertex shader source #version 330 core layout(location = 0) in vec3 position; //位置X,Y,Z layout(location = 1) in vec3 color; //颜色R,G,B out vec3 vertexColor; //输出顶点颜色 void main() { gl_Position = vec4(position, 1.0); //顶点坐标 vertexColor = color; //从顶点数据那里得到的输入颜色 }
片段着色器
//fragment shader source #version 330 core out vec4 fragColor; //像素的最终颜色 in vec3 vertexColor; //从顶点着色器传来的输入变量(名称、类型必须相同) void main() { fragColor = vec4(vertexColor, 1.0); }
程序中使用glVertexAttribPointer根据第一个参数layout值设置对应顶点属性
float 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 // 顶部 }; //生成VAO对象,缓冲ID为VAO unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO);//绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO,供之后使用 //生成VBO对象,缓冲ID为VBO unsigned int VBO; glGenBuffers(1, &VBO);//第一个参数GLsizei是要生成的缓冲对象的数量,第二个GLuint是要输入用来存储缓冲对象名称的数组 //绑定到目标对象,VBO变成了一个顶点缓冲类型 glBindBuffer(GL_ARRAY_BUFFER, VBO);//第一个就是缓冲对象的类型,第二个参数就是要绑定的缓冲对象的名称 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//数据传入缓冲内存中,GL_STATIC_DRAW:数据不会或几乎不会改变; GL_DYNAMIC_DRAW:数据会被改变很多; GL_DYNAMIC_DRAW:数据会被改变很多 //设置顶点属性指针,如何解析顶点数据 /* 第一个参数指定我们要配置的顶点属性,顶点着色器中使用layout(location = 0)定义 第二个参数指定顶点属性的大小 第三个参数指定数据的类型 第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间 第五个参数步长(Stride),它告诉我们在连续的顶点属性组之间的间隔 最后一个参数的类型是void*,数据在缓冲中起始位置的偏移量(Offset) */ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);//启用顶点属性layout(location = 0),顶点属性默认是禁用的 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);//启用顶点属性layout(location = 1),顶点属性默认是禁用的 glBindBuffer(GL_ARRAY_BUFFER, 0);//设置完属性,解绑VBO glBindVertexArray(0);//配置完VBO及其属性,解绑VAO
渲染效果