OpenGL学习笔记(三)着⾊器
⽂章⽬录
参考资料:
Shader是什么
着⾊器(Shader)是运⾏在GPU上的⼩程序。这些⼩程序为图形渲染管线的某个特定部分⽽运⾏。从基本意义上来说,着⾊器只是⼀种把输⼊转化为输出的程序。着⾊器也是⼀种⾮常独⽴的程序,因为它们之间不能相互通信;它们之间唯⼀的沟通只有通过输⼊和输出。
GLSL
着⾊器是使⽤⼀种叫GLSL的类C语⾔写成的。GLSL是为图形计算量⾝定制的,它包含⼀些针对向量和矩阵操作的有⽤特性。
结构:
1. 声明版本
2. 输⼊和输出变量
3. uniform
4. main函数
每个着⾊器的⼊⼝点都是main函数,在这个函数中我们处理所有的输⼊变量,并将结果输出到输出变量中。
典型代码:
#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;
}
当我们特别谈论到顶点着⾊器的时候,每个输⼊变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它⼀般由硬件来决定。OpenGL确保⾄少有16个包含4分量的顶点属性可⽤,但是有些硬件或许允许更多的顶点属性,你可以查询
GL_MAX_VERTEX_ATTRIBS来获取具体的上限:
GLint nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&nrAttributes);
std::cout <<"Maximum nr of vertex attributes supported: "<< nrAttributes << std::endl;
运⾏结果:
所以我的机器可以使⽤的顶点属性有4199705个。
数据类型
1. 包含C等其它语⾔⼤部分的默认基础数据类型:int、float、double、uint和bool。
2. 两种容器类型,它们会在这个教程中使⽤很多,分别是向量(Vector)和矩阵(Matrix),其中矩阵我们会在之后的教程⾥再讨论。制作查询类小程序
向量vec
GLSL中的向量是⼀个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前⾯默认基础类型的任意⼀个。它们可以是下⾯的形式(n代表分量的数量):
即默认分量是float类型的。
⼀个向量的分量可以通过vec.x这种⽅式获取,这⾥x是指这个向量的第⼀个分量。你可以分别使⽤.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜⾊使⽤rgba,或是对纹理坐标使⽤stpq访问相同的分量。
向量重组(Swizzling):
vec2 someVec;
vec4 differentVec = ;
vec3 anotherVec = w;
vec4 otherVec = + ;
我们也可以把⼀个向量作为⼀个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect =vec2(0.5f,0.7f);
vec4 result =vec4(vect,0.0f,0.0f);
vec4 otherResult =,1.0f);
输⼊与输出
我们希望每个着⾊器都有输⼊和输出,这样才能进⾏数据交流和传递。GLSL定义了in和out关键字专门来实现这个⽬的。每个着⾊器使⽤这两个关键字设定输⼊和输出,只要⼀个输出变量与下⼀个着⾊器阶段的输⼊匹配,它就会传递下去。但在顶点和⽚段着⾊器中会有点不同。
顶点着⾊器应该接收的是⼀种特殊形式的输⼊,否则就会效率低下。顶点着⾊器的输⼊特殊在,它从顶点数据中直接接收输⼊。为了定义顶点数据该如何管理,我们使⽤location这⼀元数据指定输⼊变量,顶点着⾊器需要为它的输⼊提供⼀个额外的layout标识,这样我们才能把它链接到顶点数据。
tips: 你也可以忽略layout (location = 0)标识符,通过在OpenGL代码中使⽤glGetAttribLocation查询属性位置值(Location),但是我更喜欢在着⾊器中设置它们,这样会更容易理解⽽且节省你(和OpenGL)的⼯作量。
另⼀个例外是⽚段着⾊器,它需要⼀个vec4颜⾊输出变量,因为⽚段着⾊器需要⽣成⼀个最终输出的颜⾊。如果你在⽚段着⾊器没有定义输出颜⾊,OpenGL会把你的物体渲染为⿊⾊(或⽩⾊)。
顶点着⾊器向⽚段着⾊器发送数据
顶点着⾊器⽰例代码
#version 330 core
layout (location =0) in vec3 position;// position变量的属性位置值为0
out vec4 vertexColor;// 为⽚段着⾊器指定⼀个颜⾊输出
void main()
{
gl_Position =vec4(position,1.0);// 注意我们如何把⼀个vec3作为vec4的构造器的参数
vertexColor =vec4(0.5f,0.0f,0.0f,1.0f);// 把输出变量设置为暗红⾊
}
⽚段着⾊器⽰例代码
#version 330 core
in vec4 vertexColor;// 从顶点着⾊器传来的输⼊变量(名称相同、类型相同)
out vec4 color;// ⽚段着⾊器输出的变量名可以任意命名,类型必须是vec4
void main()
{
color = vertexColor;
}
你可以看到我们在顶点着⾊器中声明了⼀个vertexColor变量作为vec4输出,并在⽚段着⾊器中声明了⼀个类似的vertexColor。由于它们名字相同且类型相同(传输条件),⽚段着⾊器中的vertexColor就和顶点着⾊器中的vertexColor链接了。由于我们在顶点着⾊器中将颜⾊设置为深红⾊,最终的⽚段也是深红⾊的。
运⾏结果:
即我们成功地从顶点着⾊器向⽚段着⾊器发送数据。
Uniform
Uniform是⼀种从CPU中的应⽤向GPU中的着⾊器发送数据的⽅式,但uniform和顶点属性有些不同:
1. uniform是全局的(Global)。全局意味着uniform变量必须在每个着⾊器程序对象中都是独⼀⽆⼆的,⽽且它可以被着⾊器程序的任意
着⾊器在任意阶段访问。
2. ⽆论你把uniform值设置成什么,uniform会⼀直保存它们的数据,直到它们被重置或更新。
我们可以在⼀个着⾊器中添加uniform关键字⾄类型和变量名前来声明⼀个GLSL的uniform。从此处开始我们就可以在着⾊器中使⽤新声明的uniform了。我们来看看这次是否能通过uniform设置三⾓形的颜⾊:
#version 330 core
out vec4 color;
uniform vec4 ourColor;// 在OpenGL程序代码中设定这个变量
void main()
{
color = ourColor;
}
声明了⼀个uniform vec4的变量ourColor,并把⽚段着⾊器的输出颜⾊设置为uniform值的内容。
uniform是全局变量,我们可以在任何着⾊器中定义它们,⽽⽆需通过顶点着⾊器作为中介。
tips: 如果你声明了⼀个uniform却在GLSL代码中没⽤过,编译器会默认移除这个变量,导致最后编译出的版本中并不会包含它。
在程序中将数据传输给uniform变量
1. 到着⾊器中uniform属性的索引/位置值。
2. 更新它的值。
这次我们不去给像素传递单独⼀个颜⾊,⽽是让它随着时间改变颜⾊:
GLfloat timeValue =glfwGetTime();
GLfloat greenValue =(sin(timeValue)/2)+0.5;// 将输⼊的值映射到 0~1
// 1.获取uniform变量的位置
GLint vertexColorLocation =glGetUniformLocation(shaderProgram,"ourColor");
glUseProgram(shaderProgram);
// 2.更新变量的值
glUniform4f(vertexColorLocation,0.0f, greenValue,0.0f,1.0f);
当前主循环代码:
void renderLoop(GLFWwindow *window, GLuint VAO, GLuint shaderProgram){
// 设置清屏时填充的颜⾊
glClearColor(0.2f,0.3f,0.3f,1.0f);
glUseProgram(shaderProgram);
// render loop
// -----------
while(!glfwWindowShouldClose(window)){
// input
processInput(window);
// render
glClear(GL_COLOR_BUFFER_BIT);
// 更新uniform颜⾊
GLfloat timeValue =glfwGetTime();
GLfloat greenValue =(sin(timeValue)/2)+0.5;
// 1.uniform变量位置
GLint vertexColorLocation =glGetUniformLocation(shaderProgram,"ourColor");
// 2.更新变量值
glUniform4f(vertexColorLocation,0.0f, greenValue,0.0f,1.0f);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES,6, GL_UNSIGNED_INT,0);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
}
运⾏结果即为颜⾊由⿊到绿不断变化的矩形。
制作三⾊渐变三⾓形
我们打算把颜⾊数据加进顶点数据中。我们将把颜⾊数据添加为3个float值⾄vertices数组。我们将把三⾓形的三个⾓分别指定为红⾊、绿⾊和蓝⾊:
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// 顶部
};
然后我们先把颜⾊信息转⼊到顶点着⾊器中,然后进⼀步传送给⽚段着⾊器。
顶点着⾊器:
#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设置为我们从顶点数据那⾥得到的输⼊颜⾊
}
⽚段着⾊器:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。