简介
GLSL 是用于编写 着色器(Shader),适合进行图形计算的语言,它包含一些针对向量和矩阵操作的特性,使渲染管线具有可编程性且运行高效。本章主要介绍在编写着色器时常用的一些语法。
变量
变量及变量类型
| 变量类型说明符 | 描述 |
| bool | 布尔型标量数据类型。 |
| int / ivec2 / ivec3 / ivec4 | 包含 1 / 2 / 3 / 4 个整型的向量。 |
| float / vec2 / vec3 / vec4 | 包含 1 / 2 / 3 / 4 个浮点型的向量。 |
| half / half2 / half3 / half4 | 包含 1 / 2 / 3 / 4 个 32 位浮点型的向量。 |
| sampler2D | 表示 2D 纹理。 |
| mat2 / mat3 / mat4 | 表示 2\*2 / 3\*3 / 4\*4 的矩阵。 |
标量
GLSL 构造标量的方式为定义变量的类型,然后定义变量的命名,最后定义变量的初始值。以下为定义标量的案例:
bool isTrue = true;
int a = 1;
float b = 0.1;向量
构造向量时的规则如下:
- 若向向量构造器提供了一个标量,则向量的所有值都会设定为该标量值。
vec4 _Vec4 = vec4(1.0);
// _Vec4 = {1.0, 1.0, 1.0, 1.0};- 若提供多个标量值或向量,则从左到右使用提供的值赋值。前提是标量或向量的数量之和要等于向量所含分量的数量。
vec2 _Vec2 = vec2(2.0,2.0)
vec4 _Vec4 = vec4(_Vec2,1.0,1.0);
// _Vec4 = {2.0, 2.0, 1.0, 1.0};获取向量不同分量的特性如下:
- 您可以依照
r,g,b,a关键字提取四个不同分量的标量值,也可以依照x,y,z,w关键字提取,两种提取和定义的方式相同。因为在着色器编码时向量可以用于表示位置也可以表示颜色值。
vec3 _Vec3 = vec3(1.0,2.0,3.0)
float _X = _Vec.x;
// _X = 1.0;
float _R = _Vec.r;
// _R = 1.0;- 您可以依照
r,g,b,a关键字或依照x,y,z,w关键字同时通过访问多个位置的标量值。值得注意的是,不能同时依照r,g,b,a关键字和依照x,y,z,w关键字访问。
vec4 _Vec4 = vec4(1.0,2.0,3.0,4.0)
vec3 _Vec3_0 = _Vec4.xyz;
// _Vec3_0 = {1.0,2.0,3.0};
vec3 _Vec3_1 = _Vec4.rgb;
// _Vec3_1 = {1.0,2.0,3.0};
vec3 _Vec3_2 = _Vec4.zzx;
// _Vec3_2 = {3.0,3.0,1.0};矩阵
您在 GLSL 内可使用 mat2 / mat3 / mat4 来表示 2 阶到 4 阶的矩阵。
GLSL 中是列矩阵存储,因此构造时,构造矩阵时会按照列顺序进行填充,矩阵构造有如下的规则:
- 矩阵可以由单个标量从左向右进行构造。
mat2 _Marixt2x2 = mat2(
1.0,2.0, //第一列;
3.0,4.0 //第二列;
);- 若只为矩阵构造器提供了一个标量,则该标量值会构造矩阵对角线上的值。
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 _Vec2_0 = vec2(1.0, 2.0);
vec2 _Vec2_1 = vec2(3.0, 4.0);
mat2 matrix2x2 = mat2(col1, col2);
/*
_Marixt2x2 = {
1.0, 2.0,
3.0, 4.0,
};
*/矩阵获取或访问某个数据有如下规则:
- 矩阵可以通过第 1 个索引获取矩阵的某一列的数据,用第 2 个索引获取该列特定位置标量值。值得注意的是,索引是从 0 开始。
mat2 _Marixt2x2 = mat2(
1.0,2.0, //第一列;
3.0,4.0 //第二列;
);
vec2 _Vec2_0 = _Marixt2x2[0];
// _Vec2_0 = {1.0,2.0};
float _f01 = _Marixt2x2[0][1];
// _f01 = 1.0;矩阵的索引必须为常量索引,常量索引的定义如下:
整形数字。
用
const限定符定义的全局变量或局部变量,不包括方法参数。循环索引。
由前述三条中的项组成的表达式。
mat2 _Marixt2x2 = mat2(
1.0,2.0, //第一列;
3.0,4.0 //第二列;
);
int _i = 1;
float _f0i = _Marixt2x2[0][_i];
// _f01 = 1.0;结构体
结构体通过 struct 关键字声明结构。结构体可以组合基本类型和数组来形成您自定义的新类型,该新类型可由不同数据类型聚合而成:
struct v2f
{
vec4 pos;
vec4 color;
vec2 uv;
};构造结构体的代码示例如下:
v2f vert()
{
v2f v;
v.pos = vec4(0.0, 0.0,0.0,0.0);
v.color = vec4(1.0, 1.0, 1.0, 1.0);
v.uv = vec2(0.5, 0.5);
return v;
}结构体之间支持通过赋值(=)和 比较(== 和 !=)运算符联系,但要求两个结构体拥有相同的类型。
数组
数组的定义和初始化规则如下:
数组必须定义长度。
数组不能在定义的同时初始化。
数组必须用常量表达式初始化。
数组不能用
const限定符定义数组。不支持多维数组。
数组声明和初始化的代码示例如下:
float _Array[4];
for(int i =0; i < 4; i ++)
{
_Array[i] = 0.0;
}限定符
储存限定符
储存限定符用于限定变量在管线中的作用。
| 储存限定符 | 描述 |
| < none:default > | 无限定符或者使用 default 限定符,常用于修饰局部变量、方法参数。 |
| const | 限定变量编译时为常量或作为只读参数。值得注意的是,变量需要在声明时完成初始化,该变量值只读,不可修改。 |
| attribute | 限定变量为只读的顶点数据,仅用在 顶点着色器(Vertex Shader)中,用于程序和顶点着色器间通信,确定顶点格式、信息。 |
| varying | 顶点着色器(Vertex Shader)传输给 片元着色器(Fragment Shader)的插值。 |
用于修饰方法参数的限定符:
| 储存限定符 | 描述 |
| < none:in > | 限定这个参数仅用于传入方法中,且可在方法中改变该参数的值。其为方法参数的默认限定符。 |
| out | 限定这个参数的值不会传入方法中,由方法内部修改并返回修改后的值。 |
| inout | 相当于 in 和 out 的结合体,定义的参数既能传入方法中,又能在方法内部修改后并返回修改后的值。 |
精度限定符
GLSL 引入了精度限定符,用于限定 整型(int)或 浮点型(float)变量的精度。在 着色器(Shader)中如果没有限定精度,则所有的整型和浮点型变量都采用高精度计算。
GLSL 支持的精度限定符包括以下种类:
| 变量类型说明符 | 描述 |
| < none:highp > | 无限定符或者使用 highp 高精度限定符。 浮点型精度范围为 -262 至 262。 整型精度范围为 -216 至 216。 |
| mediump | 中精度。 浮点型精度范围为 -214 至 214。 整型精度范围为 -210 至 210。 |
| lowp | 低精度。 浮点型精度范围为 -28 至 28。 整型精度范围为 -28 至 28。 |
控制流
条件流程
GLSL 的条件流程大体上是标准的 C 和 C++ 的条件流程,包括:
条件语句
if-else/switch-case。循环语句
for/while/do-while。跳出循环语句
return/break/continue。
值得注意的是 GLSL 没有 goto,您可以使用 discard 代替该语句的逻辑。该语句仅在 片元着色器(Fragment Shader)下有效,需要注意的是使用该语句会导致管线放弃当前 片元(Fragment),不会写入帧缓存。
在 GLSL 中,循环变量必须是常量或者编译时已知的变量,代码示例如下:
//示例中的变量 _MaxV 就是用 const 限定符定义的常量;
const float _MaxV = 5.0;
for(float i = 0.0; i < _MaxV; i ++){
//这里编辑循环中的逻辑;
}方法
GLSL 中 方法(Function)由 返回值(Return)、方法名(Function Name)和 参数(Parameters)构成,其中定义方法必须有返回值和方法名。
若无返回值,需要使用
void代替。若有返回值,方法中必须用
return字段定义该类型的返回值。
代码示例如下:
/*
定义方法:
*方法没有返回值;
*方法名为 dec;
*传入的参数是 vec4 类型,并在方法中命名为 sPos;
*/
void dec(inout float a)
{
a = a + 0.1;
}
/*
定义方法:
*返回值是 vec4 类型;
*方法名为 vert;
*传入的参数是 vec4 类型,并在方法中命名为 sPos;
*/
vec4 vert(vec4 sPos)
{
vec4 cPos;
cPos = vec4(sPos.xyz, 1.0);
return cPos;
}定义预处理宏
宏(Macros)是一种预处理指令,用于在编译时进行指令的替换,常用于定义常量、函数、条件编译等。
着色器(Shader)通过预处理宏定义多样化的动态分支,以低性能损耗确定最终的渲染效果。
在 GLSL 中常使用以下指令定义预处理宏:
| 指令 | 描述 |
#define | 定义一个预处理宏。格式是 #define Macros_Name Macros_Value 。 |
#undef | 取消宏的定义。格式是 #undef Macros_Name 。 |
#if | 编译预处理中的条件。 |
#ifdef | 判断某个宏是否被定义,若已定义,执行随后的语句。 |
#ifndef | 与 #ifdef 相反,判断某个宏是否未被定义。 |
defined | 与 #if,#elif 配合使用,判断某个宏是否被定义。 格式是 #if defined(Macros_Name) 或者 #elif defined(Macros_Name)。 |
#elif | 若 #if 或前面的 #elif 条件均不满足,则执行 #elif 之后的条件判断,相当于 else if。 |
#else | 与 #if,#ifdef,#ifndef 对应,若这些条件不满足,则执行 #else 之后的逻辑。 |
#endif | #if,#ifdef,#ifndef 这些条件命令的结束标志。 |
详细内容
关于 GLSL 详细的使用方法和语法解释,请参阅 OpenGL 官方文档:
https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.html
