第5.2章:Surface Shader结构详解

Surface Shader是Cocos Creator中最重要的着色器类型,它提供了高层次的抽象来简化复杂的光照计算。本章将深入解析Surface Shader的完整结构,帮助你理解其工作原理和最佳实践。

🎯 学习目标

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

  • Surface Shader的完整结构组�?- 顶点阶段和片元阶段的分工和联�?- 输入输出数据流的详细解析
  • 内置函数和变量的使用方法
  • Surface Shader的编译机�?

📖 Surface Shader基本结构

完整的Surface Shader模板

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
CCEffect %{
techniques:
- name: opaque
passes:
- vert: standard-vs:vert
frag: standard-fs:frag
properties:
# 材质属性定�? mainTexture: { value: grey }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
roughness: { value: 0.8, editor: { range: [0, 1] } }
metallic: { value: 0.0, editor: { range: [0, 1] } }
normalMap: { value: normal }
normalScale: { value: 1.0, editor: { range: [0, 2] } }
embeddedMacros:
CC_USE_LIGHTMAP: false
CC_RECEIVE_SHADOW: true
migrations:
macros:
CC_USE_LIGHTMAP: { from: 2.0.0, default: false }
CC_RECEIVE_SHADOW: { from: 2.0.0, default: true }
}%

# 顶点着色器程序
CCProgram standard-vs %{
precision highp float;

// 包含标准的uniform缓冲�? #include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>
#include <builtin/uniforms/cc-shadow>

// 顶点输入属�? #include <builtin/inputs/cc-position>
#include <builtin/inputs/cc-normal>
#include <builtin/inputs/cc-texcoord>
#include <builtin/inputs/cc-tangent>

// 顶点输出到片元着色器的数�? #include <builtin/outputs/cc-worldpos>
#include <builtin/outputs/cc-worldnormal>
#include <builtin/outputs/cc-uv>

// 顶点着色器主函�? vec4 vert () {
// 标准的顶点变�? StandardVertInput In;
CCVertInput(In);

// 顶点位置变换
vec4 pos = In.position;
pos = cc_matWorld * pos;
CC_TRANSFER_WORLDPOS(pos);

// 法线变换
CCTransferWorldNormalAndTangent(In.normal, In.tangent);

// UV坐标传�? CC_TRANSFER_UV(In.texCoord);

return cc_matViewProj * pos;
}
}%

# 片元着色器程序
CCProgram standard-fs %{
precision mediump float;

// 包含光照和材质相关的头文�? #include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-shadow>
#include <legacy/surface-functions>

// 片元输入数据
#include <builtin/inputs/cc-worldpos>
#include <builtin/inputs/cc-worldnormal>
#include <builtin/inputs/cc-uv>

// 材质属性uniform
uniform Properties {
sampler2D mainTexture;
sampler2D normalMap;
vec4 mainColor;
float roughness;
float metallic;
float normalScale;
};

// Surface函数 - 定义材质属�? void surf (out StandardSurface s) {
// 基础颜色采样
vec4 baseColor = texture(mainTexture, CC_GET_UV()) * mainColor;

// 法线贴图处理
vec3 normal = texture(normalMap, CC_GET_UV()).rgb;
normal = normal * 2.0 - 1.0;
normal = normalize(CC_GET_WORLDNORMAL() + normal * normalScale);

// 设置Surface属�? s.albedo = baseColor.rgb;
s.normal = normal;
s.roughness = roughness;
s.metallic = metallic;
s.alpha = baseColor.a;
}

// 片元着色器主函�? vec4 frag () {
StandardSurface s;
surf(s);

// 应用标准光照模型
vec4 color = CC_STANDARD_SURFACE_ENTRY(s);

return color;
}
}%

🔧 结构组件详解

1. CCEffect配置�?

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
CCEffect %{
# 技术定�? techniques:
- name: opaque # 技术名�? passes: # 渲染Pass列表
- vert: vs:vert # 顶点着色器入口
frag: fs:frag # 片元着色器入口

# 渲染状态配�? rasterizerState:
cullMode: back # 背面剔除
depthStencilState:
depthTest: true # 深度测试
depthWrite: true # 深度写入
blendState:
targets:
- blend: false # 混合模式

# 材质属�? properties:
mainTexture: { value: grey, editor: { displayName: "Main Texture" } }
mainColor: { value: [1,1,1,1], editor: { type: color, displayName: "Main Color" } }

# 编译时宏定义
embeddedMacros:
CC_USE_LIGHTMAP: false
CC_RECEIVE_SHADOW: true

# 版本迁移配置
migrations:
properties:
mainColor: { from: 1.0.0, to: mainTint }
}%

2. 顶点着色器结构

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
CCProgram vertex-shader %{
precision highp float;

// === 包含系统头文�?===
#include <builtin/uniforms/cc-global> // 全局uniform
#include <builtin/uniforms/cc-local> // 本地变换矩阵
#include <builtin/uniforms/cc-shadow> // 阴影相关

// === 输入定义 ===
#include <builtin/inputs/cc-position> // 位置输入
#include <builtin/inputs/cc-normal> // 法线输入
#include <builtin/inputs/cc-texcoord> // UV坐标输入
#include <builtin/inputs/cc-tangent> // 切线输入
#include <builtin/inputs/cc-color> // 顶点颜色输入

// === 输出定义 ===
#include <builtin/outputs/cc-worldpos> // 世界位置输出
#include <builtin/outputs/cc-worldnormal> // 世界法线输出
#include <builtin/outputs/cc-uv> // UV坐标输出
#include <builtin/outputs/cc-fog> // 雾效输出

// === 自定义输入输�?===
out vec3 v_viewPos; // 视图空间位置
out mat3 v_tbn; // TBN矩阵

// === 顶点着色器主函�?===
vec4 vert () {
// 1. 初始化标准顶点输入结�? StandardVertInput In;
CCVertInput(In);

// 2. 位置变换
vec4 worldPos = cc_matWorld * In.position;
CC_TRANSFER_WORLDPOS(worldPos);

// 3. 法线和切线变�? CCTransferWorldNormalAndTangent(In.normal, In.tangent);

// 4. UV坐标处理
CC_TRANSFER_UV(In.texCoord);

// 5. 自定义计�? v_viewPos = (cc_matView * worldPos).xyz;

// 6. 构建TBN矩阵
vec3 worldNormal = normalize((cc_matWorldIT * vec4(In.normal, 0.0)).xyz);
vec3 worldTangent = normalize((cc_matWorld * vec4(In.tangent.xyz, 0.0)).xyz);
vec3 worldBitangent = cross(worldNormal, worldTangent) * In.tangent.w;
v_tbn = mat3(worldTangent, worldBitangent, worldNormal);

// 7. 返回裁剪空间位置
return cc_matViewProj * worldPos;
}
}%

3. 片元着色器结构

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
CCProgram fragment-shader %{
precision mediump float;

// === 包含光照相关头文�?===
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-shadow>
#include <legacy/surface-functions>
#include <legacy/lighting-models>

// === 输入数据 ===
#include <builtin/inputs/cc-worldpos>
#include <builtin/inputs/cc-worldnormal>
#include <builtin/inputs/cc-uv>

// 自定义输�? in vec3 v_viewPos;
in mat3 v_tbn;

// === 材质属�?===
uniform Properties {
sampler2D mainTexture;
sampler2D normalMap;
sampler2D aoMap;
sampler2D roughnessMap;
sampler2D metallicMap;
vec4 mainColor;
float roughness;
float metallic;
float normalScale;
float aoStrength;
};

// === Surface函数实现 ===
void surf (out StandardSurface s) {
// 基础颜色
vec4 baseColor = texture(mainTexture, CC_GET_UV()) * mainColor;

// 法线贴图
vec3 normalTex = texture(normalMap, CC_GET_UV()).rgb * 2.0 - 1.0;
vec3 worldNormal = normalize(v_tbn * normalTex * vec3(normalScale, normalScale, 1.0));

// 金属度和粗糙�? float metallicValue = texture(metallicMap, CC_GET_UV()).r * metallic;
float roughnessValue = texture(roughnessMap, CC_GET_UV()).r * roughness;

// 环境遮蔽
float ao = texture(aoMap, CC_GET_UV()).r;
ao = mix(1.0, ao, aoStrength);

// 填充Surface结构
s.albedo = baseColor.rgb;
s.normal = worldNormal;
s.roughness = roughnessValue;
s.metallic = metallicValue;
s.ao = ao;
s.alpha = baseColor.a;
}

// === 片元着色器主函�?===
vec4 frag () {
StandardSurface s;
surf(s);

// 应用标准光照
vec4 color = CC_STANDARD_SURFACE_ENTRY(s);

return color;
}
}%

🔄 数据流分�?

顶点阶段数据�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
graph TD
A[顶点属性输入] --> B[顶点着色器]
B --> C[变换计算]
C --> D[插值器输出]

A1[a_position] --> B
A2[a_normal] --> B
A3[a_texCoord] --> B
A4[a_tangent] --> B

B --> C1[位置变换]
B --> C2[法线变换]
B --> C3[UV处理]

C1 --> D1[v_worldPos]
C2 --> D2[v_worldNormal]
C3 --> D3[v_uv]

片元阶段数据�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
graph TD
A[插值输入] --> B[Surface函数]
B --> C[材质属性]
C --> D[光照计算]
D --> E[最终颜色]

A1[v_worldPos] --> B
A2[v_worldNormal] --> B
A3[v_uv] --> B

B --> C1[albedo]
B --> C2[normal]
B --> C3[roughness]
B --> C4[metallic]

C1 --> D
C2 --> D
C3 --> D
C4 --> D

D --> E[最终渲染结果]

数据传递示�?

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
// 顶点着色器中的数据准备
vec4 vert() {
// 输入数据获取
StandardVertInput input;
CCVertInput(input); // 从顶点属性填充input结构

// 变换计算
vec4 worldPos = cc_matWorld * input.position;
vec3 worldNormal = normalize((cc_matWorldIT * vec4(input.normal, 0)).xyz);

// 数据传递到片元着色器
CC_TRANSFER_WORLDPOS(worldPos); // 传递世界坐�? CC_TRANSFER_WORLDNORMAL(worldNormal); // 传递世界法�? CC_TRANSFER_UV(input.texCoord); // 传递UV坐标

return cc_matViewProj * worldPos;
}

// 片元着色器中的数据接收
vec4 frag() {
// 接收插值后的数�? vec3 worldPos = CC_GET_WORLDPOS(); // 获取世界坐标
vec3 worldNormal = CC_GET_WORLDNORMAL(); // 获取世界法线
vec2 uv = CC_GET_UV(); // 获取UV坐标

// 使用数据进行渲染计算
// ...
}

🏗�?内置函数和变�?

变换相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// === 位置变换函数 ===
// 传递世界坐标到片元着色器
CC_TRANSFER_WORLDPOS(vec4 worldPos);
// 获取世界坐标
vec3 CC_GET_WORLDPOS();

// === 法线变换函数 ===
// 传递世界法线和切线
CCTransferWorldNormalAndTangent(vec3 normal, vec4 tangent);
// 获取世界法线
vec3 CC_GET_WORLDNORMAL();

// === UV坐标函数 ===
// 传递UV坐标
CC_TRANSFER_UV(vec2 uv);
// 获取UV坐标
vec2 CC_GET_UV();

// === 光照相关函数 ===
// 计算阴影衰减
float CC_SHADOW_ATTEN();
// 获取主光源方�?vec3 CC_GET_MAIN_LIGHT_DIR();
// 获取主光源颜�?vec3 CC_GET_MAIN_LIGHT_COLOR();

内置Uniform变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// === 全局Uniform (cc-global) ===
uniform CCGlobal {
highp mat4 cc_matView; // 视图矩阵
highp mat4 cc_matViewInv; // 视图逆矩�? highp mat4 cc_matProj; // 投影矩阵
highp mat4 cc_matProjInv; // 投影逆矩�? highp mat4 cc_matViewProj; // 视图投影矩阵
highp mat4 cc_matViewProjInv; // 视图投影逆矩�? highp vec4 cc_cameraPos; // 摄像机位�? mediump vec4 cc_time; // 时间变量
mediump vec4 cc_screenSize; // 屏幕尺寸
};

// === 本地Uniform (cc-local) ===
uniform CCLocal {
highp mat4 cc_matWorld; // 世界矩阵
highp mat4 cc_matWorldIT; // 世界逆转置矩�? highp vec4 cc_lightingMapUVParam; // 光照贴图UV参数
};

// === 光照Uniform ===
uniform CCLight {
vec4 cc_mainLitDir; // 主光源方�? vec4 cc_mainLitColor; // 主光源颜�? vec4 cc_ambientSky; // 环境光天空颜�? vec4 cc_ambientGround; // 环境光地面颜�?};

Surface结构定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 标准Surface结构
struct StandardSurface {
vec3 albedo; // 基础颜色 (漫反射颜�?
vec3 normal; // 世界空间法线
vec3 emissive; // 自发光颜�? float roughness; // 粗糙�?[0,1]
float metallic; // 金属�?[0,1]
float ao; // 环境遮蔽 [0,1]
float alpha; // 透明�?[0,1]
};

// 自定义Surface结构示例
struct CustomSurface {
vec3 albedo;
vec3 normal;
vec3 specular; // 自定义镜面反射颜�? float shininess; // 光泽�? float transparency; // 透明�? vec3 subsurface; // 次表面散射颜�?};

⚙️ 编译机制详解

宏展开过程

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
// 原始代码
CC_TRANSFER_WORLDPOS(worldPos);

// 宏展开�?#if CC_USE_POSITION
cc_worldPos = worldPos.xyz;
#endif

// 进一步展开
varying vec3 cc_worldPos; // 在顶点着色器中声明为输出
cc_worldPos = worldPos.xyz; // 在顶点着色器中赋�?```

### 条件编译

```glsl
// 基于功能开关的条件编译
#if CC_USE_LIGHTMAP
// 光照贴图相关代码
uniform sampler2D cc_lightMap;
vec3 lightmapColor = texture(cc_lightMap, lightmapUV).rgb;
s.albedo *= lightmapColor;
#endif

#if CC_RECEIVE_SHADOW
// 阴影接收代码
float shadowAtten = CCGetShadowAtten();
s.albedo *= shadowAtten;
#endif

#if CC_USE_VERTEX_COLOR
// 顶点颜色混合
s.albedo *= v_color.rgb;
s.alpha *= v_color.a;
#endif

着色器变体生成

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
// 着色器变体配置示例
interface ShaderVariant {
name: string;
macros: { [key: string]: boolean | string | number };
}

const variants: ShaderVariant[] = [
{
name: "basic",
macros: {
CC_USE_LIGHTMAP: false,
CC_RECEIVE_SHADOW: false,
CC_USE_VERTEX_COLOR: false
}
},
{
name: "with-lightmap",
macros: {
CC_USE_LIGHTMAP: true,
CC_RECEIVE_SHADOW: true,
CC_USE_VERTEX_COLOR: false
}
},
{
name: "with-vertex-color",
macros: {
CC_USE_LIGHTMAP: false,
CC_RECEIVE_SHADOW: true,
CC_USE_VERTEX_COLOR: true
}
}
];

🔧 高级结构技�?

自定义顶点变�?

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
CCProgram custom-vertex %{
// 自定义顶点变换函�? vec4 customVertexTransform(vec4 position, vec3 normal) {
// 顶点动画
float time = cc_time.x;
position.y += sin(position.x * 2.0 + time) * 0.1;

// GPU实例化支�? #if CC_USE_INSTANCING
position = cc_matWorld * position;
#endif

return position;
}

vec4 vert() {
StandardVertInput input;
CCVertInput(input);

// 应用自定义变�? vec4 pos = customVertexTransform(input.position, input.normal);

// 标准处理流程
CC_TRANSFER_WORLDPOS(pos);
// ...

return cc_matViewProj * pos;
}
}%

多Pass渲染结构

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
CCEffect %{
techniques:
- name: multi-pass
passes:
# Pass 1: 深度预渲�? - vert: depth-vs:vert
frag: depth-fs:frag
rasterizerState:
cullMode: back
depthStencilState:
depthTest: true
depthWrite: true
blendState:
targets:
- blend: false
colorMask: 0 # 不写入颜�? properties: &depth-props
depthOffset: { value: 0.001 }

# Pass 2: 主渲�? - vert: main-vs:vert
frag: main-fs:frag
rasterizerState:
cullMode: back
depthStencilState:
depthTest: true
depthWrite: false # 不写入深�? depthFunc: equal # 深度相等测试
properties: &main-props
mainTexture: { value: white }
mainColor: { value: [1,1,1,1] }
}%

实例化渲染支�?

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
CCProgram instanced-vertex %{
#if CC_USE_INSTANCING
// 实例化数据输�? in vec4 a_matWorld0;
in vec4 a_matWorld1;
in vec4 a_matWorld2;
in vec4 a_matWorld3;
in vec4 a_instanceColor;

// 获取实例化世界矩�? mat4 getInstanceWorldMatrix() {
return mat4(a_matWorld0, a_matWorld1, a_matWorld2, a_matWorld3);
}
#endif

vec4 vert() {
StandardVertInput input;
CCVertInput(input);

#if CC_USE_INSTANCING
mat4 worldMatrix = getInstanceWorldMatrix();
vec4 worldPos = worldMatrix * input.position;
#else
vec4 worldPos = cc_matWorld * input.position;
#endif

CC_TRANSFER_WORLDPOS(worldPos);
return cc_matViewProj * worldPos;
}
}%

💡 最佳实�?

代码组织

  1. *模块化设�?: 将复杂功能拆分为独立的CCProgram�?2. 统一命名: 使用一致的变量和函数命名规�?3. 注释完整: 为复杂逻辑添加详细注释
  2. 版本控制: 使用migrations处理版本升级

性能优化

  1. 精度选择: 顶点着色器使用highp,片元着色器使用mediump
  2. 条件编译: 使用宏开关控制可选功�?3. 变体管理: 合理控制着色器变体数量
  3. 资源共享: 复用常用的着色器代码�?

调试技�?

1
2
3
4
5
6
7
// 调试可视化函�?vec4 debugVisualize(int mode, StandardSurface s) {
if (mode == 0) return vec4(s.albedo, 1.0);
if (mode == 1) return vec4(s.normal * 0.5 + 0.5, 1.0);
if (mode == 2) return vec4(s.roughness, s.roughness, s.roughness, 1.0);
if (mode == 3) return vec4(s.metallic, s.metallic, s.metallic, 1.0);
return vec4(1.0, 0.0, 1.0, 1.0); // 错误颜色
}

理解Surface Shader结构是掌握Cocos Creator着色器编程的关键。通过深入了解其组成和工作原理,你将能够创建更加复杂和高效的着色器效果�?

*下一步学�?