第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;
}

🎭 函数定义与调用

函数定义语法

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
// 基本函数定义
float calculateDistance(vec3 pointA, vec3 pointB) {
vec3 diff = pointA - pointB;
return length(diff);
}

// 带修饰符的函数参数
void transformUV(in vec2 uv, in vec2 tiling, in vec2 offset, out vec2 result) {
result = uv * tiling + offset;
}

// 返回结构体的函数
struct LightingResult {
vec3 diffuse;
vec3 specular;
};

LightingResult calculateLighting(vec3 normal, vec3 lightDir, vec3 viewDir) {
LightingResult result;

float NdotL = max(0.0, dot(normal, lightDir));
result.diffuse = vec3(NdotL);

vec3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0.0, dot(normal, halfVector));
result.specular = vec3(pow(NdotH, 32.0));

return result;
}

函数参数修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// in: 输入参数(默认)
float processValue(in float input) {
return input * 2.0;
}

// out: 输出参数
void getColorComponents(vec3 color, out float r, out float g, out float b) {
r = color.r;
g = color.g;
b = color.b;
}

// inout: 输入输出参数
void adjustBrightness(inout vec3 color, float factor) {
color *= factor;
}

// 使用示例
vec3 myColor = vec3(0.5, 0.7, 0.9);
adjustBrightness(myColor, 1.5); // myColor被修改

函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数重载示例
float blend(float base, float overlay) {
return base * overlay;
}

vec3 blend(vec3 base, vec3 overlay) {
return base * overlay;
}

vec4 blend(vec4 base, vec4 overlay) {
return vec4(base.rgb * overlay.rgb, base.a);
}

// 编译器会根据参数类型自动选择匹配的函数
float result1 = blend(0.5, 0.8);
vec3 result2 = blend(vec3(0.5), vec3(0.8));

📏 存储限定符详解

顶点着色器输入输出

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
// 顶点着色器
CCProgram vs-main %{
precision highp float;

// 输入顶点属性
in vec3 a_position; // 顶点位置
in vec3 a_normal; // 顶点法线
in vec2 a_texCoord; // 纹理坐标
in vec4 a_color; // 顶点颜色
in vec4 a_tangent; // 切线向量

// 输出到片元着色器
out vec3 v_worldPos; // 世界空间位置
out vec3 v_worldNormal; // 世界空间法线
out vec2 v_uv; // UV坐标
out vec4 v_color; // 顶点颜色
out vec3 v_tangent; // 切线
out vec3 v_bitangent; // 副切线

vec4 vert() {
vec4 pos = vec4(a_position, 1.0);
v_worldPos = (cc_matWorld * pos).xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0.0)).xyz);
v_uv = a_texCoord;
v_color = a_color;

return cc_matViewProj * cc_matWorld * pos;
}
}%

片元着色器输入输出

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
30
31
32
33
34
// 片元着色器
CCProgram fs-main %{
precision highp float;

// 从顶点着色器接收
in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;
in vec4 v_color;

// Uniform变量
uniform sampler2D mainTexture;
uniform sampler2D normalMap;

uniform Properties {
vec4 mainColor;
float metallic;
float roughness;
float normalScale;
};

// 输出颜色
vec4 frag() {
vec4 baseColor = texture(mainTexture, v_uv) * mainColor * v_color;
vec3 normal = normalize(v_worldNormal);

// 简单光照计算
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float NdotL = max(0.0, dot(normal, lightDir));

baseColor.rgb *= NdotL;
return baseColor;
}
}%

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
// 材质属性块
uniform MaterialProperties {
vec4 albedoColor;
vec4 emissionColor;
float metallic;
float roughness;
float normalScale;
float aoIntensity;
} u_material;

// 光照属性块
uniform LightingData {
vec3 lightDirection;
vec3 lightColor;
float lightIntensity;
vec3 ambientColor;
} u_lighting;

// 使用示例
vec4 frag() {
vec3 baseColor = u_material.albedoColor.rgb;
float metallic = u_material.metallic;
vec3 lightColor = u_lighting.lightColor * u_lighting.lightIntensity;

// 光照计算...
return vec4(baseColor * lightColor, 1.0);
}

📐 精度限定符

精度声明

1
2
3
4
5
6
7
8
9
// 全局精度声明
precision highp float; // 高精度浮点数
precision mediump int; // 中精度整数
precision lowp sampler2D; // 低精度纹理采样器

// 变量级精度声明
highp vec3 worldPosition; // 世界位置需要高精度
mediump vec2 textureCoord; // UV坐标中精度足够
lowp vec4 vertexColor; // 顶点颜色低精度即可

精度选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 高精度 (highp) - 32位
// 适用于:位置、坐标变换、复杂计算
highp vec3 v_worldPos;
highp mat4 u_mvpMatrix;

// 中精度 (mediump) - 16位
// 适用于:UV坐标、法线、大部分计算
mediump vec3 v_normal;
mediump vec2 v_uv;
mediump float u_time;

// 低精度 (lowp) - 10位
// 适用于:颜色、小范围数值
lowp vec4 v_color;
lowp float u_alpha;

平台兼容性处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 跨平台精度处理
#ifdef GL_ES
precision mediump float; // 移动端使用中精度
#else
precision highp float; // 桌面端使用高精度
#endif

// 条件精度宏
#ifdef HIGH_PRECISION
#define PRECISION highp
#else
#define PRECISION mediump
#endif

PRECISION vec3 worldPosition;
PRECISION float metallicValue;

🔧 预处理器指令

宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
// 常量宏
#define PI 3.14159265359
#define TWO_PI (2.0 * PI)
#define HALF_PI (PI * 0.5)

// 函数宏
#define saturate(x) clamp(x, 0.0, 1.0)
#define lerp(a, b, t) mix(a, b, t)
#define square(x) ((x) * (x))

// 复杂宏定义
#define TRANSFORM_TEX(tex, name) (tex.xy * name##_ST.xy + name##_ST.zw)
#define SAMPLE_TEXTURE_LOD(tex, sampler, coord, lod) textureLod(tex, coord, lod)

条件编译

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
30
31
// 功能开关
#ifdef USE_NORMAL_MAP
vec3 tangentNormal = texture(normalMap, v_uv).xyz * 2.0 - 1.0;
vec3 normal = normalize(TBN * tangentNormal);
#else
vec3 normal = normalize(v_normal);
#endif

// 质量级别
#if QUALITY_LEVEL >= 2
// 高质量代码
vec3 reflection = reflect(viewDir, normal);
vec3 envColor = textureLod(envMap, reflection, roughness * maxLod).rgb;
#elif QUALITY_LEVEL == 1
// 中等质量代码
vec3 envColor = texture(envMap, normal).rgb;
#else
// 低质量代码
vec3 envColor = vec3(0.1, 0.1, 0.2);
#endif

// 平台特定代码
#ifdef GL_ES
// WebGL/移动端特定代码
precision mediump float;
#define SAMPLE_COUNT 4
#else
// 桌面端代码
precision highp float;
#define SAMPLE_COUNT 16
#endif

Include指令

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
// 包含内置头文件
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>
#include <builtin/functions/common>

// 包含自定义头文件
#include <common/lighting-functions>
#include <common/noise-functions>
#include <common/color-space>

// 示例:lighting-functions.chunk
vec3 calculateDirectionalLight(vec3 normal, vec3 lightDir, vec3 lightColor) {
float NdotL = max(0.0, dot(normal, lightDir));
return lightColor * NdotL;
}

vec3 calculatePointLight(vec3 worldPos, vec3 normal, vec3 lightPos, vec3 lightColor, float range) {
vec3 lightVector = lightPos - worldPos;
float distance = length(lightVector);
float attenuation = 1.0 - smoothstep(0.0, range, distance);

vec3 lightDir = normalize(lightVector);
float NdotL = max(0.0, dot(normal, lightDir));

return lightColor * NdotL * attenuation;
}

📚 小结

本章全面介绍了GLSL的基础语法:

  • 数据类型:标量、向量、矩阵、结构体、数组的定义和使用
  • 控制流程:条件语句、循环语句、分支控制的语法和最佳实践
  • 函数系统:函数定义、参数修饰符、函数重载的使用方法
  • 存储限定符:in/out/uniform等限定符的作用和使用场景
  • 精度控制:不同精度级别的选择和平台兼容性处理
  • 预处理器:宏定义、条件编译、文件包含的高级用法

掌握这些GLSL基础语法后,你就可以开始编写更复杂的着色器程序了。

下一章: 第3.1章:创建和使用着色器

💡 学习建议

  1. 循序渐进:先掌握基本数据类型,再学习复杂的控制结构
  2. 动手实践:每个语法点都要通过实际代码验证
  3. 注意精度:合理选择精度级别,平衡性能和质量
  4. 学会调试:使用宏定义和条件编译进行调试

🔗 参考资源

继续深入学习,你将掌握强大的着色器编程能力!