第2.2章:GLSL基础语法

GLSL(OpenGL Shading Language)是专为图形计算设计的着色器语言,它包含针对向量和矩阵操作的特性,使渲染管线具有可编程性。本章将详细介绍在Cocos Creator Shader开发中常用的GLSL语法知识。

🎯 学习目标

通过本章学习,你将掌握:

  • GLSL变量类型和数据结构
  • 控制流程语句的使用
  • 函数定义和调用方法
  • 存储限定符的作用和用法
  • 精度限定符的设置和优化
  • 预处理宏定义的高级技巧

📊 变量和数据类型

基本数据类型

GLSL支持多种数据类型,每种都有其特定的用途和默认值:

变量类型说明默认值Cocos Shader可选项
bool布尔型标志false
int/ivec2/ivec3/ivec4整型向量(1-4维)0/[0,0]/[0,0,0]/[0,0,0,0]
float/vec2/vec3/vec4浮点型向量(1-4维)0.0/[0,0]/[0,0,0]/[0,0,0,0]
sampler2D2D纹理采样器defaultblack, grey, white, normal, default
samplerCube立方体纹理采样器default-cubeblack-cube, white-cube, default-cube
mat2/mat3/mat4矩阵(2x2/3x3/4x4)单位矩阵

标量操作

标量的构造和操作与C语言类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 浮点数声明
float timeValue = 1.0;
float speed = 2.5;

// 整数声明
int count = 10;
int maxItems = 100;

// 布尔值声明
bool isVisible = true;
bool hasTexture = false;

// 基本运算
float result = timeValue * speed + 1.0;
bool condition = count < maxItems;

向量操作详解

向量是GLSL中最重要的数据类型,支持多种构造和访问方式。

向量构造

1
2
3
4
5
6
7
8
9
10
11
12
// 单一值构造 - 所有分量相同
vec4 color1 = vec4(1.0); // {1.0, 1.0, 1.0, 1.0}
vec3 position = vec3(0.5); // {0.5, 0.5, 0.5}

// 多值构造 - 逐一指定
vec4 color2 = vec4(1.0, 0.5, 0.2, 1.0); // RGBA
vec3 velocity = vec3(1.0, -2.0, 0.0); // XYZ

// 混合构造 - 向量与标量组合
vec2 uv = vec2(0.5, 0.8);
vec4 color3 = vec4(color2.rgb, 0.5); // 使用前一个向量的RGB,透明度0.5
vec4 transform = vec4(uv, 0.0, 1.0); // uv作为XY,Z=0,W=1

向量分量访问

GLSL提供多种向量分量访问方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vec4 color = vec4(1.0, 0.5, 0.2, 1.0);

// 颜色分量访问 (r, g, b, a)
float red = color.r; // 获取红色分量
float alpha = color.a; // 获取透明度分量

// 坐标分量访问 (x, y, z, w)
float x = color.x; // 等同于color.r
float w = color.w; // 等同于color.a

// 多分量访问(Swizzling)
vec3 rgb = color.rgb; // {1.0, 0.5, 0.2}
vec3 bgr = color.bgr; // {0.2, 0.5, 1.0} - 反序
vec2 rg = color.rg; // {1.0, 0.5}
vec4 rgba = color.rgba; // 完整向量

// 重复分量访问
vec3 rrr = color.rrr; // {1.0, 1.0, 1.0}
vec2 xy = color.xy; // {1.0, 0.5}

// 分量修改
color.rgb = vec3(0.8, 0.6, 0.4); // 只修改RGB,保持Alpha
color.xy = vec2(0.0, 1.0); // 只修改XY分量

矩阵操作详解

矩阵在3D变换中至关重要,GLSL提供完整的矩阵支持:

矩阵构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 单位矩阵构造
mat4 identity = mat4(1.0); // 对角线为1.0,其余为0.0

// 完整矩阵构造(列优先存储)
mat3 transform = mat3(
1.0, 0.0, 0.0, // 第一列
0.0, 1.0, 0.0, // 第二列
0.0, 0.0, 1.0 // 第三列
);

// 向量构造矩阵
vec3 col1 = vec3(1.0, 0.0, 0.0);
vec3 col2 = vec3(0.0, 1.0, 0.0);
vec3 col3 = vec3(0.0, 0.0, 1.0);
mat3 matrix = mat3(col1, col2, col3);

矩阵访问和操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mat4 transform = mat4(1.0);

// 列访问
vec4 firstColumn = transform[0]; // 获取第一列
vec4 secondColumn = transform[1]; // 获取第二列

// 元素访问
float element = transform[0][0]; // 第一列第一行
transform[1][1] = 2.0; // 修改第二列第二行

// 矩阵运算
mat4 modelMatrix = mat4(1.0);
mat4 viewMatrix = mat4(1.0);
mat4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;

// 向量与矩阵运算
vec4 position = vec4(1.0, 2.0, 3.0, 1.0);
vec4 transformedPos = mvpMatrix * position;

⚠️ 重要提醒:为避免内存对齐问题,引擎要求使用Uniform限定符的矩阵必须是4阶矩阵(mat4),2阶和3阶矩阵不能作为Uniform变量使用。

结构体定义

结构体允许组合不同类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 材质结构体定义
struct Material {
vec3 diffuse; // 漫反射颜色
vec3 specular; // 镜面反射颜色
float shininess; // 光泽度
sampler2D diffuseMap; // 漫反射贴图
};

// 光照结构体定义
struct Light {
vec3 position; // 光源位置
vec3 color; // 光源颜色
float intensity; // 光强度
float range; // 光照范围
};

// 结构体实例化
Material material = Material(
vec3(0.8, 0.6, 0.4), // diffuse
vec3(1.0, 1.0, 1.0), // specular
32.0, // shininess
mainTexture // diffuseMap
);

// 结构体访问
vec3 materialColor = material.diffuse;
float gloss = material.shininess;

数组操作

GLSL的数组使用有特定规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 数组声明(必须指定大小)
float weights[4];
vec3 positions[8];
mat4 boneMatrices[64];

// 数组初始化(必须在运行时进行)
for(int i = 0; i < 4; i++) {
weights[i] = 0.25; // 平均权重
}

// 常量数组示例
const int MAX_LIGHTS = 8;
Light lights[MAX_LIGHTS];

// 数组访问
for(int i = 0; i < MAX_LIGHTS; i++) {
if(lights[i].intensity > 0.0) {
// 处理有效光源
}
}

🔄 控制流程语句

条件语句

GLSL支持标准的条件控制结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// if-else 语句
float lightIntensity = 0.8;
vec3 finalColor;

if(lightIntensity > 0.5) {
finalColor = vec3(1.0, 1.0, 0.0); // 亮黄色
} else if(lightIntensity > 0.2) {
finalColor = vec3(0.5, 0.5, 0.0); // 暗黄色
} else {
finalColor = vec3(0.0, 0.0, 0.0); // 黑色
}

// 三元操作符
vec3 resultColor = (lightIntensity > 0.5) ? vec3(1.0) : vec3(0.0);

// 条件函数(推荐用于性能优化)
float threshold = step(0.5, lightIntensity); // lightIntensity >= 0.5 ? 1.0 : 0.0
vec3 optimizedColor = mix(vec3(0.0), vec3(1.0), threshold);

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// for循环
vec3 accumColor = vec3(0.0);
for(int i = 0; i < 4; i++) {
accumColor += texture(inputTexture, uv + offset[i]).rgb;
}
accumColor /= 4.0; // 平均值

// while循环
int counter = 0;
float value = 1.0;
while(value > 0.01 && counter < 10) {
value *= 0.5;
counter++;
}

// do-while循环
int iteration = 0;
do {
// 至少执行一次
iteration++;
} while(iteration < maxIterations);

⚠️ 性能提示:在片元着色器中避免使用过多循环,特别是嵌套循环,这会严重影响GPU性能。

分支控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// break语句
for(int i = 0; i < MAX_SAMPLES; i++) {
vec4 sample = texture(inputTexture, sampleUV[i]);
if(sample.a < 0.01) {
break; // 提前退出循环
}
// 处理有效样本
}

// continue语句
vec3 totalColor = vec3(0.0);
int validSamples = 0;
for(int i = 0; i < SAMPLE_COUNT; i++) {
vec4 sample = texture(inputTexture, sampleUV[i]);
if(sample.a < 0.1) {
continue; // 跳过无效样本
}
totalColor += sample.rgb;
validSamples++;
}

// discard语句(仅在片元着色器中)
vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);
if(texColor.a < alphaThreshold) {
discard; // 丢弃透明像素
}
return texColor;
}

🎭 函数定义与调用

函数定义语法