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

本章将手把手教你如何在Cocos Creator中创建自定义着色器,从最基础的步骤开始,到完整的着色器应用。通过实际操作,你将掌握着色器开发的完整流程。

🎯 学习目标

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

  • 在Cocos Creator中创建Effect文件的方法
  • 编写基础的顶点和片元着色器
  • 创建和配置材质
  • 将着色器应用到游戏对象上
  • 调试和测试着色器效果
  • 着色器的版本管理和优化

🛠️ 环境准备

开发工具要求

  • Cocos Creator: 3.8.x 版本
  • VSCode: 建议安装Cocos Effect扩展
  • 图片编辑工具: Photoshop/GIMP(制作测试纹理)

项目设置

确保项目已正确配置:

1
2
3
4
// 项目设置检查
- 渲染管线:Built-in Forward
- 图形API:根据目标平台选择
- Shader版本:300 es(推荐)

📁 第一步:创建着色器文件

1.1 在资源管理器中创建

  1. 选择目录:在assets目录下创建shaders文件夹
  2. 右键菜单:选择”创建 → Effect”
  3. 命名文件:输入MyFirstShader.effect

1.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
CCEffect %{
techniques:
- name: opaque
passes:
- vert: vs:vert
frag: fs:frag
properties:
mainTexture: { value: white }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
}%

CCProgram vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;
in vec2 a_texCoord;

out vec2 v_uv;

vec4 vert () {
vec4 pos = vec4(a_position, 1);
pos = cc_matViewProj * cc_matWorld * pos;
v_uv = a_texCoord;
return pos;
}
}%

CCProgram fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
uniform sampler2D mainTexture;
uniform MainColor {
vec4 mainColor;
};

vec4 frag () {
vec4 color = texture(mainTexture, v_uv);
return color * mainColor;
}
}%

🎨 第二步:编写你的第一个着色器

2.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
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
CCEffect %{
techniques:
- name: opaque
passes:
- vert: rainbow-vs:vert
frag: rainbow-fs:frag
properties:
mainTexture: { value: white }
colorSpeed: { value: 1.0, editor: { range: [0, 5], step: 0.1 } }
brightness: { value: 1.0, editor: { range: [0, 2], step: 0.1 } }
}%

CCProgram rainbow-vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;
in vec2 a_texCoord;

out vec2 v_uv;
out vec3 v_worldPos;

vec4 vert () {
vec4 pos = vec4(a_position, 1);
v_worldPos = (cc_matWorld * pos).xyz;
pos = cc_matViewProj * cc_matWorld * pos;
v_uv = a_texCoord;
return pos;
}
}%

CCProgram rainbow-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec3 v_worldPos;

uniform sampler2D mainTexture;
uniform RainbowParams {
float colorSpeed;
float brightness;
};

// HSV到RGB转换函数
vec3 hsv2rgb(vec3 hsv) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y);
}

vec4 frag () {
// 获取基础纹理
vec4 texColor = texture(mainTexture, v_uv);

// 基于时间和位置计算色调
float hue = fract(cc_time.x * colorSpeed + v_worldPos.x * 0.1 + v_worldPos.z * 0.1);
float saturation = 0.8;
float value = brightness;

// 转换为RGB
vec3 rainbowColor = hsv2rgb(vec3(hue, saturation, value));

// 与纹理混合
vec3 finalColor = texColor.rgb * rainbowColor;

return vec4(finalColor, texColor.a);
}
}%

2.2 详细解析

CCEffect部分解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
techniques:
- name: opaque # 渲染技术名称
passes:
- vert: rainbow-vs:vert # 指定顶点着色器程序和入口函数
frag: rainbow-fs:frag # 指定片元着色器程序和入口函数
properties: # 材质属性定义
mainTexture: { value: white } # 主纹理,默认白色
colorSpeed: {
value: 1.0, # 默认值
editor: { range: [0, 5], step: 0.1 } # 编辑器配置
}
brightness: {
value: 1.0,
editor: { range: [0, 2], step: 0.1 }
}

顶点着色器解析

1
2
3
4
5
6
7
8
9
10
11
12
13
in vec3 a_position;      // 顶点位置输入
in vec2 a_texCoord; // 纹理坐标输入

out vec2 v_uv; // 传递给片元着色器的UV
out vec3 v_worldPos; // 传递世界空间位置

vec4 vert () {
vec4 pos = vec4(a_position, 1); // 转换为齐次坐标
v_worldPos = (cc_matWorld * pos).xyz; // 计算世界坐标
pos = cc_matViewProj * cc_matWorld * pos; // 变换到裁剪空间
v_uv = a_texCoord; // 传递UV坐标
return pos; // 返回最终位置
}

片元着色器解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// HSV颜色空间转RGB的数学函数
vec3 hsv2rgb(vec3 hsv) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y);
}

vec4 frag () {
// 1. 采样基础纹理
vec4 texColor = texture(mainTexture, v_uv);

// 2. 计算动态色调
float hue = fract(cc_time.x * colorSpeed + v_worldPos.x * 0.1 + v_worldPos.z * 0.1);

// 3. 生成彩虹颜色
vec3 rainbowColor = hsv2rgb(vec3(hue, 0.8, brightness));

// 4. 与纹理混合
vec3 finalColor = texColor.rgb * rainbowColor;

return vec4(finalColor, texColor.a);
}

📦 第三步:创建材质

3.1 创建Material文件

  1. 右键资源管理器:选择”创建 → Material”
  2. 命名材质RainbowMaterial.mtl
  3. 选择Effect:在Inspector中选择你的Effect文件

3.2 配置材质属性

在Material的Inspector面板中:

1
2
3
4
// 可配置的属性会显示在Inspector中
- mainTexture: 拖入测试纹理
- colorSpeed: 调整颜色变化速度 (0-5)
- brightness: 调整亮度 (0-2)

3.3 材质预览

Cocos Creator会在Material Inspector中提供实时预览,你可以:

  • 调整参数查看实时效果
  • 切换预览几何体(球体/立方体/平面)
  • 观察参数变化对效果的影响

🎮 第四步:应用到游戏对象

4.1 创建测试场景

1
2
3
4
// 创建基础测试场景
1. 新建Scene
2. 添加3D对象:CubeSphere
3. 确保对象有MeshRenderer组件

4.2 应用材质

有两种方式应用材质:

方式一:拖拽应用

1
2
3
4
// 直接拖拽Material文件到MeshRenderer组件的Material槽位
1. 选择3D对象
2. 找到MeshRenderer组件
3.RainbowMaterial.mtl拖入Materials数组

方式二:脚本应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ccclass('ShaderApplicator')
export class ShaderApplicator extends Component {
@property(Material)
rainbowMaterial: Material = null!;

@property(MeshRenderer)
meshRenderer: MeshRenderer = null!;

start() {
// 应用材质到渲染器
this.meshRenderer.setMaterial(this.rainbowMaterial, 0);
}

// 运行时切换材质
switchMaterial(newMaterial: Material) {
this.meshRenderer.setMaterial(newMaterial, 0);
}
}

4.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
@ccclass('ShaderController')
export class ShaderController extends Component {
@property(MeshRenderer)
meshRenderer: MeshRenderer = null!;

@property
animateSpeed: boolean = true;

private currentSpeed: number = 1.0;

start() {
// 获取材质实例
const material = this.meshRenderer.getMaterial(0);
if (material) {
// 设置初始参数
material.setProperty('colorSpeed', this.currentSpeed);
}
}

update(deltaTime: number) {
if (this.animateSpeed) {
// 动态调整参数
this.currentSpeed = Math.sin(director.getTotalTime()) * 2.0 + 2.5;

const material = this.meshRenderer.getMaterial(0);
if (material) {
material.setProperty('colorSpeed', this.currentSpeed);
}
}
}

// 通过UI控制参数
onSpeedSliderChanged(slider: Slider) {
const material = this.meshRenderer.getMaterial(0);
if (material) {
material.setProperty('colorSpeed', slider.progress * 5.0);
}
}
}

🔍 第五步:调试和测试

5.1 常见问题排查

编译错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检查语法错误
1. 查看控制台编译信息
2. 检查变量名拼写
3. 确认include路径正确
4. 验证uniform块定义

// 常见错误示例
// ❌ 错误:变量未定义
uniform UnknownBlock {
float value;
};

// ✅ 正确:使用正确的块名
uniform RainbowParams {
float colorSpeed;
};

渲染问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 检查渲染设置
// 1. 确认MeshRenderer组件正确设置
// 2. 检查Material是否正确应用
// 3. 验证Technique选择
// 4. 检查相机设置

@ccclass('ShaderDebugger')
export class ShaderDebugger extends Component {
@property(MeshRenderer)
meshRenderer: MeshRenderer = null!;

start() {
// 调试材质状态
const material = this.meshRenderer.getMaterial(0);
if (material) {
console.log('Material Effect:', material.effectAsset?.name);
console.log('Technique Count:', material.techniqueIndex);

// 检查属性值
const speed = material.getProperty('colorSpeed');
console.log('Color Speed:', speed);
}
}
}

5.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
@ccclass('PerformanceTester')
export class PerformanceTester extends Component {
@property
objectCount: number = 100;

@property(Material)
testMaterial: Material = null!;

@property(Mesh)
testMesh: Mesh = null!;

start() {
// 创建大量对象测试性能
for (let i = 0; i < this.objectCount; i++) {
this.createTestObject(i);
}
}

createTestObject(index: number) {
const node = new Node(`TestObject_${index}`);
const meshRenderer = node.addComponent(MeshRenderer);

meshRenderer.mesh = this.testMesh;
meshRenderer.setMaterial(this.testMaterial, 0);

// 随机位置
node.position = new Vec3(
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20
);

this.node.addChild(node);
}
}

📈 第六步:优化和迭代

6.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
29
// 性能优化技巧

// 1. 精度优化
precision mediump float; // 移动端使用中等精度

// 2. 计算优化
// ❌ 避免重复计算
vec4 frag() {
float time = cc_time.x;
vec3 color = vec3(sin(time), cos(time), sin(time * 2.0));
return vec4(color, 1.0);
}

// ✅ 优化后
vec4 frag() {
float time = cc_time.x;
float sinTime = sin(time);
vec3 color = vec3(sinTime, cos(time), sin(time * 2.0));
return vec4(color, 1.0);
}

// 3. 条件编译优化
#if USE_COMPLEX_CALCULATION
// 复杂计算
vec3 complexColor = calculateComplex(v_uv);
#else
// 简单计算
vec3 complexColor = v_uv.xxx;
#endif

6.2 功能扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 添加更多可配置参数
CCEffect %{
techniques:
- name: opaque
passes:
- vert: rainbow-vs:vert
frag: rainbow-fs:frag
properties:
mainTexture: { value: white }
colorSpeed: { value: 1.0, range: [0, 5], step: 0.1 }
brightness: { value: 1.0, range: [0, 2], step: 0.1 }

# 新增属性
saturation: { value: 0.8, range: [0, 1], step: 0.1 }
waveAmplitude: { value: 0.1, range: [0, 1], step: 0.01 }
useWorldPosition: { value: true }

# 纹理动画
uvSpeed: { value: [0, 0], editor: { tooltip: "UV scrolling speed" } }
}%

📚 小结

通过本章学习,你已经掌握了:

  • 着色器创建:从Effect文件到Material的完整流程
  • 基础编程:顶点和片元着色器的编写方法
  • 参数控制:静态和动态参数的配置和修改
  • 应用实践:将着色器应用到实际游戏对象中
  • 调试技巧:常见问题的排查和解决方法
  • 性能优化:着色器的优化策略和最佳实践

这是着色器开发的基础,掌握了这些技能后,你就可以开始创建更复杂和有趣的视觉效果了。

下一章: 第3.2章:着色器材质系统

💡 实践建议

  1. 从简单开始:先创建简单的颜色变化效果,再逐步增加复杂度
  2. 多做实验:尝试修改参数,观察效果变化
  3. 注重性能:始终考虑着色器的性能影响
  4. 建立资源库:保存有用的着色器片段和函数

🔗 参考资源

继续你的着色器开发之旅,下一章我们将深入学习材质系统!