GLSL 语法简介
GLSL 是为图形计算量身定制的用于编写着色器的语言,它包含一些针对向量和矩阵操作的特性,使渲染管线具有可编程性。本章主要介绍在编写 Shader 时常用的一些语法,包括以下几个方面:
- 变量
- 语句
- 限定符
变量
变量及变量类型
变量类型 | 说明 |
---|---|
bool | 布尔型标量数据类型 |
int/ivec2/ivec3/ivec4 | 包含 1/2/3/4 个整型向量 |
float/vec2/vec3/vec4 | 包含 1,2,3,4 个浮点型向量 |
sampler2D | 表示 2D 纹理 |
samplerCube | 表示立方体纹理 |
mat[2..3] | 表示 2x2 和 3x3 的矩阵 |
mat4 | 表示 4x4 的矩阵 |
标量
构造标量的方式和 C 语言一致:
float floatValue = 1.0;
bool booleanValue = false;
向量
构造向量时的规则如下:
- 若向向量构造器提供了一个标量,则向量的所有值都会设定为该标量值
- 若提供多个标量值或向量,则从左到右使用提供的值赋值。前提是标量或向量的数量之和要等于向量构造器的数量
vec4 myVec4 = vec4(1.0); // myVec4 = {1.0, 1.0, 1.0, 1.0}
vec2 myVec2 = vec2(0.5, 0.5); // myVec2 = {0.5, 0.5}
vec4 newVec4 = vec4(1.0, 1.0, myVec2);// newVec4 = {1.0, 1.0, 0.5, 0.5}
向量可以通过 r, g, b, a
或 x, y, z, w
进行访问,也可以同时访问多个角标:
vec4 myVec4_0 = vec4(1.0); // myVec4_0 = { 1.0, 1.0, 1.0, 1.0 }
vec4 myVec4 = vec4(1.0, 2.0, 3.0, 4.0); // myVec4 = { 1.0, 2.0, 3.0, 4.0 }
float x = myVec4.x; // x = 1.0;
vec3 myVec3_0 = myVec4.xyz; // myVec3_0 = { 1.0, 2.0, 3.0 }
vec3 myVec3_1 = myVec4.rgb; // myVec3_1 = { 1.0, 2.0, 3.0 }
vec3 myVec3_2 = myVec4.zyx; // myVec3_2 = { 3.0, 2.0, 1.0 }
vec3 myVec3_3 = myVec4.xxx; // myVec3_3 = { 1.0, 1.0, 1.0 }
矩阵
在 GLSL 内可构造 mat[2..4] 来表示 2 阶到 4 阶的矩阵。
矩阵构造有如下的规则:
- 若只为矩阵构造器提供了一个标量,则该值会构造矩阵对角线上的值
- 矩阵可以由多个向量构造
- 矩阵可以由单个标量从左到右进行构造
mat4 marixt4x4 = mat4(1.0); // marixt4x4 = { 1.0, 0.0, 0.0, 0.0,
// 0.0, 1.0, 0.0, 0.0
// 0.0, 0.0, 1.0, 0.0
// 0.0, 0.0, 0.0, 1.0 }
vec2 col1 = vec2(1.0, 0.0);
vec2 col2 = vec2(1.0, 0.0);
mat2 matrix2x2 = mat2(coll1, col2);
// GLSL 是列矩阵存储,因此构造时,构造器会按照列顺序进行填充
mat3 matrix3x3 = mat3(0.0, 0.0, 0.0, // 第一列
0.0, 0.0, 0.0, // 第二列
0.0, 0.0, 0.0); // 第三列
注意:为避免 implicit padding,引擎规定若要使用 Uniform 限定符的矩阵,必须是 4 阶矩阵,2 阶和 3 阶的矩阵不可作为 Uniform 变量。
矩阵的访问:
矩阵可以通过索引访问不同的列:
mat2 matrix2x2 = mat2(0.0, 0.0, 0.0, 0.0);
vec4 myVec4 = vec4(matrix2x2[0], matrix2x2[1]);
vec2 myVec2 = matrix2x2[0];
// 访问第一列的第一个元素
float value = matrix2x2[0][0];
matrix2x2[1][1] = 2.0;
结构体
结构体的形成和 C 语言类似,可由不同数据类型聚合而成:
struct myStruct
{
vec4 position;
vec4 color;
vec2 uv;
};
构造结构体的代码示例如下:
myStruct structVar = myStruct(vec4(0.0, 0.0,0.0,0.0), vec4(1.0, 1.0, 1.0, 1.0), vec2(0.5, 0.5));
结构体支持赋值(=)和比较(==,!=)运算符,但要求两个结构体拥有相同的类型且组件分量(component-wise)都必须相同。
数组
数组的用法和 C 语言类似,规则如下:
- 数组必须声明长度
- 数组不能在声明的同时初始化
- 数组必须由常量表达式初始化
- 数组不能用
const
修饰 - 不支持多维数组
数组声明和初始化的代码示例如下:
float array[4];
for(int i =0; i < 4; i ++)
{
array[i] = 0.0;
}
语句
控制流程
GLSL 支持标准的 C/C++ 控制流程,包括:
-
if-esle
/switch-case
-
for
/while
/do-while
-
break
/continue
/return
- 没有
goto
,若要跳出可使用discard
。该语句仅在片元着色器下有效,需要注意的是使用该语句会导致管线放弃当前片元,不会写入帧缓存
if-else
的用法和 C 语言一致,代码示例如下:
if(v_uvMode >= 3.0) {
i.uv = v_uv0 * v_uvSizeOffset.xy + v_uvSizeOffset.zw;
} else if (v_uvMode >= 2.0) {
i.uv = fract(v_uv0) * v_uvSizeOffset.xy + v_uvSizeOffset.zw;
} else if (v_uvMode >= 1.0) {
i.uv = evalSlicedUV(v_uv0) * v_uvSizeOffset.xy + v_uvSizeOffset.zw;
} else {
i.uv = v_uv0;
}
在 GLSL 中,循环变量必须是常量或者编译时已知,代码示例如下:
const float value = 10.;
for(float i = 0.0; i < value; i ++){
...
}
错误示例:
float value = 10.;
// 错误,value 不为常量
for(float i =0.0; i < value; i ++){
...
}
函数
GLSL 的函数由 返回值、函数名 和 参数 构成,其中返回值和函数名是必须的。若无返回值,需要使用 void
代替。
注意:GLSL 的函数不能递归。
代码示例如下:
void scaleMatrix (inout mat4 m, float s){
m[0].xyz *= s;
m[1].xyz *= s;
m[2].xyz *= s;
}
限定符
存储限定符
存储限定符用于描述变量在管线中的作用。
限定符 | 说明 |
---|---|
< none:default > | 无限定符或者使用 default ,常用语局部变量,函数参数 |
const | 编译时为常量或作为参数时只读 |
attribute | 应用程序和顶点着色器间通信,用于确定顶点格式 |
uniform | 应用程序和着色器之间交互数据。在顶点着色器和片元着色器中保持一致 |
varying | 顶点着色器传输给片元着色器的插值 |
attribute
attribute变量只存在于顶点shader,用于顶点shader接收CPU传来的数据,多为顶点相关的数据。attribute给了CPU一个向shader传递值的接口,CPU通过变量名获取attribute变量在GPU的地址,使用glVertexAttribPointer命令向其赋值。通常CPU将模型的顶点数据传给shader,shader会依次对每个顶点通过一系列矩阵操作,确定其最终位置,赋值给顶点shader内建的输出变量gl_position供渲染管线下一步使用。
uniform
uniform常量在shader中不可变,不同于attribute变量,其在顶点shader和片段shader中都可以使用,如果两个shader中都声明了同一个uniform变量,那么他们值是相同的。CPU通过变量名获取uniform变量在GPU的地址,使用glUniform1i命令向其赋值。通常CPU通过uniform常量向GPU传递纹理等数据块。
varying
顶点shader用varying变量向片段shader传递值。使用时,在顶点shader与片段shader中定义同一个varying变量,顶点shader在顶点执行完之后,会自动将值传递给片段shader。varying变量CPU是不可见的,一般是顶点shader中计算并赋值。通常varying变量用来传递顶点的颜色值、纹理坐标。渐变量有一个重要的特点是可以自动插值。从CPU传给GPU的只是模型每个顶点的属性,对于顶点之间的线或面上的像素属性(主要是其颜色和纹理坐标),渐变量可以根据该像素在顶点间的位置自动插值出来。 这个特点也是为符合其用途的需要。
参数限定符
GLSL 中函数的参数限定符包括以下几种:
限定符 | 说明 |
---|---|
< none:in > | 缺省限定符,和 C 语言的值传递类似,指明传入的参数传递的是值,函数内不会修改传入的值 |
inout | 类似于 C 语言的引用,参数的值会传入函数并返回函数内修改的值 |
out | 参数的值不会传入函数,由函数内部修改并返回修改后的值 |
精度限定符
GLSL 引入了精度限定符,用于指定整型或浮点型变量的精度。精度限定符可使着色器的编写者明确定义着色器变量计算时使用的精度。在 在 Shader 头部声明的精度应用于整个 Shader,是所有基于浮点型的变量的默认精度,同时也可以定义单个变量的精度。在 Shader 中如果没有指定默认精度,则所有的整型和浮点型变量都采用高精度计算。
GLSL 支持的精度限定符包括以下几种:
限定符 | 说明 |
---|---|
highp | 高精度。 |
浮点型精度范围为 [-262, 262]
整型精度范围为 [-216, 216]。 |
| mediump | 中精度。
浮点型精度范围为 [-214, 214]
整型精度范围为 [-210, 210]。 |
| lowp | 低精度。
浮点型精度范围为 [-28, 28]
整型精度范围为 [-28, 28]。 |
代码示例如下:
highp mat4 cc_matWorld;
mediump vec2 dir;
lowp vec4 cc_shadowColor;