着色器(shader)
其本质是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特点部分而运行。 从基本意义上来讲,着色器只是一种把输入转化为输出的程序。着色器之间相互独立,因为它们之间不能相互通信,它们之间唯一的沟通是通过输入和输出。
GLSL
OpenGL驱动使用的着色器语言是一种叫做GLSL的类C语言写成的,它包含一些针对向量和矩阵操作的有用特性。(D3D11驱动是使用的着色器语言是HLSL)。
着色器的开头总是要声明版本,接着是输入和输出变量,uniform和main函数。
每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将计算后的最终结果赋值给输出变量。
典型的着色器结构如下(伪代码):
//声明使用的GLSL版本,不同版本会存在接口差异
#version version_number
//输入变量
in type in_variable_name;
in type in_variable_name;
//输出变量
out type out_variable_name;
//uniform 变量
uniform type uniform_name;
int main()
{
// 处理输入并进行一些图形操作
..
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
注意 每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它一般由硬件决定。OpenGl确保至少有16个包含4分量的顶点属性可用,大部分情况下是够用的。
向量数据类型
GLSL中的向量是一个可以包含有1,2,3或者4个分量的容器,分量类型可以是其中默认基础类型的任意一个。它们可以是下面的形式(n
代表分量的数量):
类型 | 含义 |
---|---|
vecn | 包含n个float分量的默认向量 |
bvecn | 包含n个bool分量的向量 |
ivecn | 包含n个int分量的向量 |
uvecn | 包含n个unsigned int 分量的向量 |
dvecn | 包含n个double分量的向量 |
大多数情况下我们使用vecn, 因为float可以满足大部分要求了。
一个向量的分量获取方式有多种,可以是xyzw, rgba或纹理stpq的形式。
向量还能允许一些灵活有趣的分量选择组合方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
我们也可以把一个向量作为一个参数传给不同的向量构造函数。
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
输入与输出
着色器之间通过输入和输出进行数据交流和传递。GLSL因此定义了in
和out
关键字专门来实现这个目的。每个着色器使用这两个关键字设定输入和输出,只要一个输出变量和下一个着色器阶段的输入匹配, 它就会传递下去。
顶点着色器接收的是一种特殊形式的输入,否则会导致效率低下。顶点着色器的输入特殊在,它从顶点数据中直接接收输入。为了定义顶点数据是如何管理的,我们可以在着色器中使用location
字段来指定输入变量位置。
location
标识符也可以忽略,应用层在配置顶点属性时会指定location位置。
片段着色器需要一个vec4
颜色输出变量,因为片段着色器需要生成一个最终输出的颜色值。
示例如下:
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; //位置属性
out vec4 vertexColor; //为片段着色器指定的颜色输出
void main()
{
gl_Postion = vec4(aPos, 1.0);
vertexColor = vec4(0.5, 0.0, 0.0, 1.0);
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量 (名称相同,类型相同)
void main()
{
FragColor = vertexColor;
}
Uniform
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但和顶点属性有些不同。首先,uniform是全局的(global),这意味着uniform变量可以被任意阶段的任意着色器对象访问。第二点,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
在着色器程序中无法对uniform的值进行更改
使用uniform实例如下
大致步骤: 一,我们需要找到着色器中uniform属性的索引/位置值。二,调用glUniform相关方法设置其值。
需要注意的是: 更新一个uniform之前,你必须先使用程序(调用glUseProgram),针对当前激活的着色器程序中的uniform。
// 随着时间变化更新uniform的值
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);