OpenGL学习 (二)
这一节回顾OpenGL的着色器语言GLSL。
GLSL数据类型
和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix)。
全数据类型
变量类别 | 变量类型 | 备注 |
---|---|---|
空 | void | 用于标识无参函数或者无返回值函数:void main(void); |
标量 | bool/int/float | 布尔类型、整型、浮点型 |
布尔型向量 | bvec2/bvec3/bvec4 | 其中b表示向量类型,数字表示向量的分量数 |
整型向量 | ivec2/ivec3/ivec4 | 其中i表示向量类型,数字表示向量的分量数 |
浮点型向量 | vec2/vec3/vec4 | 默认情况下是float类型,数字表示向量的分量数 |
浮点型矩阵 | mat2/mat3/mat4 | 数字表示矩阵的列数,行数和列数相同 |
2D texture | sampler2D | 2D纹理,仅能作为uniform变量 |
Cubemap(立方体) texture | samplerCube | 立方体纹理,仅能作为uniform变量 |
结构体 | struct | 类似于C语言结构体,把多个变量聚合在一起 |
数组 | array | GLSL只支持1维数组,数据类型可以是标量类型、向量类型、矩阵类型、结构体类型 |
向量
GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):
类型 | 含义 |
---|---|
vecn | 包含n 个float分量的默认向量 |
bvecn | 包含n 个bool分量的向量 |
ivecn | 包含n 个int分量的向量 |
uvecn | 包含n 个unsigned int分量的向量 |
dvecn | 包含n 个double分量的向量 |
大多数时候我们使用vecn,因为float足够满足大多数要求了。
一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
1 | vec2 someVec; |
你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。
我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
1 | vec2 vect = vec2(0.5, 0.7); |
矩阵
这里主要谈论的是矩阵的构造,这个比较容易弄混淆!
矩阵构造器主要有两种构造方式:
1.单标量参数:用于主对角线上分量的初始化,其他分量皆为0.0。
2.多标量参数、向量参数、或者标量和向量混合参数:按照参数顺序初始化矩阵的所有分量(列优先),需要保证参数个数(向量参数的分量拆分开)不少于矩阵分量个数。
1 | // 通过多个标量为矩阵的各个分量赋值 |
非常需要注意的是你用向量初始化矩阵的时候每个向量初始化的是矩阵的一列!而在CG语言(包括unity的shaderLab)中我们初始化的时候是先初始化的一行!两者的区别在于前者OpenGL是列优先的,而后者是行优先的。因此这两种方式构造矩阵刚好是一个转置的关系!这在之后理解切线空间TBN矩阵的时候会有影响,如果不理解会感到疑惑为什么两者用类似的方法构造矩阵在运算的时候表示的意思总是相反的 (假如你要用别的着色器语言实现的话你会碰到这个问题)!
输入与输出
GLSL定义了in
和out
关键字,每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。顶点着色器和片元着色器输入输出的模板略有不同:
顶点着色器
顶点着色器应该接收的是一种特殊形式的输入,否则就会效率低下。顶点着色器的输入特殊在,它从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。为了指定location我们需要给顶点着色器的输入提供一个额外的layout标识,这样我们才能关联到相应的顶点数据,如:layout (location = 0)
。
顶点着色器可以给片段着色器传递变量。我们必须在发送方着色器(顶点着色器)中声明一个输出,在接收方着色器中(片段着色器)声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。
1 |
|
片段着色器
片段着色器的输入数据,来自于顶点着色器,我们通过声明in变量来接收来自顶点着色器的输出。
片段着色器的输出,需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
1 |
|
Uniform
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问(我们可以在任何着色器中定义它们,而无需通过顶点着色器作为中介)。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误,记住这点!
给Uniform添加数据
我们首先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。
1 | //为查询函数提供着色器程序和uniform的名字(这是我们希望获得的位置值的来源) |
用glGetUniformLocation
查询uniform ourColor的位置值,如果glGetUniformLocation返回-1就代表没有找到这个位置值。最后,我们可以通过glUniform4f函数设置uniform值。注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置uniform的。
glUniform
这个函数有一个特定的后缀,标识设定的uniform的类型。可能的后缀有:
后缀 | 含义 |
---|---|
f | 函数需要一个float作为它的值 |
i | 函数需要一个int作为它的值 |
ui | 函数需要一个unsigned int作为它的值 |
3f | 函数需要3个float作为它的值 |
fv | 函数需要一个float向量/数组作为它的值 |
uniform变量使用流程
1)在需要使用uniform变量的shader(vertex shader或者fragment shader都可以)中声明变量。
2)给uniform变量添加数据
1 | /*这里是让三角形颜色慢慢变换例子程序的循环部分*/ |
参考资料