Cocos Creator Shader踩坑记录:闪白、水波纹、水面倒影效果实现

Cocos Creator Shader踩坑记录

写Shader时踩了不少坑,记录一下GLSL基础语法和实际用过的特效实现,包括闪白、水波纹、水面倒影这些效果。

Shader基础知识

Shader类型

类型 简称 作用 处理阶段
顶点着色器 Vertex Shader 处理顶点数据 顶点处理阶段
片段着色器 Fragment Shader 计算像素颜色 光栅化阶段
几何着色器 Geometry Shader 处理几何图元 可选阶段

Cocos Creator主要用顶点着色器和片段着色器。

Cocos Creator Shader结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Effect文件结构
CCEffect %{
techniques:
- passes:
- vert: vs // 顶点着色器入口
frag: fs // 片段着色器入口
properties: // 材质属性定义
texture: { value: white }
alphaThreshold: { value: 0.5 }
}%

// 顶点着色器
CCProgram vs %{
// 顶点处理代码
}%

// 片段着色器
CCProgram fs %{
// 颜色计算代码
}%

GLSL基础语法

数据类型

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
// 标量类型
float a = 1.0; // 浮点数
int b = 1; // 整数
bool c = true; // 布尔值

// 向量类型
vec2 v2 = vec2(1.0, 2.0); // 二维向量
vec3 v3 = vec3(1.0, 2.0, 3.0); // 三维向量
vec4 v4 = vec4(1.0); // 四维向量(所有分量都是1.0)

// 分量访问
v4.x, v4.y, v4.z, v4.w // 位置分量
v4.r, v4.g, v4.b, v4.a // 颜色分量
v4.s, v4.t, v4.p, v4.q // 纹理坐标分量

// 矩阵类型
mat2 m2 = mat2(1.0, 0.0, 0.0, 1.0); // 2x2矩阵
mat3 m3 = mat3(1.0); // 3x3单位矩阵
mat4 m4 = mat4(
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
); // 4x4矩阵

// 采样器类型(纹理)
uniform sampler2D texture; // 2D纹理
uniform samplerCube cubeTexture; // 立方体纹理

修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
// 存储修饰符
in vec2 a_position; // 输入属性(顶点数据)
out vec4 v_color; // 输出变量(传递给片段着色器)
uniform float u_time; // 统一变量(全局参数)

// 精度修饰符
precision highp float; // 高精度浮点
precision mediump float; // 中精度浮点
precision lowp float; // 低精度浮点

// 精度建议
// 顶点着色器:highp
// 片段着色器:mediump或highp(根据平台)

内置函数

通用函数

函数 说明 示例
abs(x) 绝对值 abs(-1.0) = 1.0
sign(x) 符号函数 sign(-5.0) = -1.0
floor(x) 向下取整 floor(2.7) = 2.0
ceil(x) 向上取整 ceil(2.2) = 3.0
fract(x) 小数部分 fract(2.7) = 0.7
mod(x, y) 取模 mod(10.0, 3.0) = 1.0
min(x, y) 取最小值 min(2.0, 3.0) = 2.0
max(x, y) 取最大值 max(2.0, 3.0) = 3.0
clamp(x, minVal, maxVal) 限制范围 clamp(10.0, 0.0, 1.0) = 1.0
mix(x, y, a) 线性插值 mix(0.0, 1.0, 0.5) = 0.5
step(edge, x) 阶梯函数 step(0.5, 0.7) = 1.0
smoothstep(edge0, edge1, x) 平滑阶梯 平滑过渡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 函数使用示例
void main() {
float x = 0.7;

// step函数:x < 0.5返回0,否则返回1
float y1 = step(0.5, x); // 返回1.0

// smoothstep函数:平滑过渡
float y2 = smoothstep(0.3, 0.7, x);

// mix函数:颜色混合
vec3 colorA = vec3(1.0, 0.0, 0.0); // 红色
vec3 colorB = vec3(0.0, 0.0, 1.0); // 蓝色
vec3 mixedColor = mix(colorA, colorB, 0.5); // 紫色

// clamp函数:限制范围
float val = clamp(x, 0.0, 1.0);
}

三角函数

函数 说明 参数
radians(degrees) 角度转弧度 角度值
degrees(radians) 弧度转角度 弧度值
sin(angle) 正弦 弧度
cos(angle) 余弦 弧度
tan(angle) 正切 弧度
asin(x) 反正弦 -1到1
acos(x) 反余弦 -1到1
atan(y, x) 反正切 y/x
1
2
3
4
5
6
7
// 正弦波动画
uniform float cc_time;

void main() {
float t = cc_time.x;
float wave = sin(t * 2.0);
}

指数函数

函数 说明 示例
pow(x, y) x的y次幂 pow(2.0, 3.0) = 8.0
exp(x) e的x次幂 exp(1.0) = 2.718…
log(x) 自然对数 log(2.718) = 1.0
exp2(x) 2的x次幂 exp2(3.0) = 8.0
log2(x) 以2为底的对数 log2(8.0) = 3.0
sqrt(x) 平方根 sqrt(9.0) = 3.0
inversesqrt(x) 平方根的倒数 inversesqrt(4.0) = 0.5

几何函数

函数 说明 返回值
length(v) 向量长度 float
distance(p0, p1) 两点距离 float
dot(v1, v2) 点积 float
cross(v1, v2) 叉积(仅vec3) vec3
normalize(v) 归一化 同类型向量
reflect(I, N) 反射向量 同类型向量
refract(I, N, eta) 折射向量 同类型向量
1
2
3
4
5
6
7
8
9
// 计算光照方向
vec3 lightDir = normalize(lightPos - fragPos);

// 计算漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

// 计算反射
vec3 reflectDir = reflect(-lightDir, normal);

Cocos Creator内置Uniform

时间相关

1
2
3
4
5
6
7
8
9
10
11
12
uniform CCGlobal {
vec4 cc_time; // x: 秒级时间, y: 上一帧时间, z: 游戏开始到现在的时间
vec4 cc_sinTime; // 正弦化时间
vec4 cc_cosTime; // 余弦化时间
vec4 cc_deltaTime; // 帧间隔时间
};

// 使用示例
void main() {
float t = cc_time.x * 0.5; // 慢速时间
float wave = sin(t * 10.0); // 波浪动画
}

矩阵变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uniform CCGlobal {
mat4 cc_matView; // 视图矩阵
mat4 cc_matViewInv; // 视图逆矩阵
mat4 cc_matProj; // 投影矩阵
mat4 cc_matViewProj; // 视图投影矩阵
};

uniform CCLocal {
mat4 cc_matWorld; // 世界矩阵
mat4 cc_matWorldIT; // 世界矩阵的逆转置(用于法线变换)
};

// 顶点变换
vec4 worldPos = cc_matWorld * vec4(a_position, 1.0);
vec4 viewPos = cc_matView * worldPos;
vec4 clipPos = cc_matProj * viewPos;

颜色相关

1
2
3
4
5
6
in vec4 v_color;  // 从顶点着色器传入的颜色

void main() {
vec4 texColor = texture(texture, v_uv0);
gl_FragColor = texColor * v_color;
}

实战特效案例

闪白效果(Flash White)

游戏中角色受击时的视觉反馈。

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
// FlashWhite.effect
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
blendState:
targets:
- blend: true
rasterizerState:
cullMode: none
properties:
texture: { value: white }
alphaThreshold: { value: 0.5 }
}%

CCProgram vs %{
precision highp float;

#include <cc-global>
#include <cc-local>

in vec3 a_position;
in vec4 a_color;
out vec4 v_color;

#if USE_TEXTURE
in vec2 a_uv0;
out vec2 v_uv0;
#endif

void main () {
vec4 pos = vec4(a_position, 1);

#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif

#if USE_TEXTURE
v_uv0 = a_uv0;
#endif

v_color = a_color;
gl_Position = pos;
}
}%

CCProgram fs %{
precision highp float;

#include <alpha-test>
#include <texture>

in vec4 v_color;

uniform Params {
float hurt; // 闪白强度,0-1
};

#if USE_TEXTURE
in vec2 v_uv0;
uniform sampler2D texture;
#endif

void main () {
vec4 o = vec4(1, 1, 1, 1);

#if USE_TEXTURE
CCTexture(texture, v_uv0, o);
#endif

o *= v_color;
ALPHA_TEST(o);

// 闪白效果:增加白色分量
#if USE_BGRA
gl_FragColor = o.bgra;
#else
gl_FragColor = o.rgba + vec4(0.5, 0.5, 0.5, 0) * hurt;
#endif
}
}%

TypeScript使用

1
2
3
4
5
6
7
8
9
10
// 普通Sprite闪白
flashWhite(): void {
const material = this.node.getComponent(cc.Sprite).getMaterial(0);
material.setProperty("hurt", 1);

// 0.1秒后恢复
this.scheduleOnce(() => {
material.setProperty("hurt", 0);
}, 0.1);
}

Spine动画闪白

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
// FlashWhiteSpine.effect
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
blendState:
targets:
- blend: true
rasterizerState:
cullMode: none
properties:
texture: { value: white }
alphaThreshold: { value: 0.5 }
}%

CCProgram vs %{
precision highp float;

#include <cc-global>
#include <cc-local>

in vec3 a_position;
in vec4 a_color;

#if USE_TINT
in vec4 a_color0;
#endif

in vec2 a_uv0;
out vec2 v_uv0;
out vec4 v_light;

#if USE_TINT
out vec4 v_dark;
#endif

void main () {
mat4 mvp;

#if CC_USE_MODEL
mvp = cc_matViewProj * cc_matWorld;
#else
mvp = cc_matViewProj;
#endif

v_uv0 = a_uv0;
v_light = a_color;

#if USE_TINT
v_dark = a_color0;
#endif

gl_Position = mvp * vec4(a_position, 1);
}
}%

CCProgram fs %{
precision highp float;

uniform sampler2D texture;
in vec2 v_uv0;
in vec4 v_light;

#if USE_TINT
in vec4 v_dark;
#endif

#include <alpha-test>
#include <texture>

uniform Params {
float hurt;
};

void main () {
vec4 texColor = vec4(1.0);
CCTexture(texture, v_uv0, texColor);
vec4 finalColor;

#if USE_TINT
// Spine双色着色
finalColor.a = v_light.a * texColor.a;
finalColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
#else
finalColor = texColor * v_light + vec4(0.5, 0.5, 0.5, 0) * hurt;
#endif

ALPHA_TEST(finalColor);
gl_FragColor = finalColor;
}
}%
1
2
3
4
5
6
7
8
9
10
// Spine动画闪白
flashWhiteSpine(): void {
const skeleton = this.node.getComponent(sp.Skeleton);
const material = skeleton.getMaterial(0);
material.setProperty("hurt", 1);

this.scheduleOnce(() => {
material.setProperty("hurt", 0);
}, 0.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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// WaterRipple.effect
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
depthStencilState:
depthTest: false
depthWrite: false
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
rasterizerState:
cullMode: none
properties:
texture: { value: white }
texture2: { value: white }
alphaThreshold: { value: 0.5 }
}%

CCProgram vs %{
precision highp float;

#include <cc-global>
#include <cc-local>

in vec3 a_position;
in vec4 a_color;
out vec4 v_color;
in vec2 a_uv0;
out vec2 v_uv0;

void main () {
vec4 pos = vec4(a_position, 1);

#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif

v_uv0 = a_uv0;
v_color = a_color;
gl_Position = pos;
}
}%

CCProgram fs %{
precision highp float;

#include <cc-global>
#include <alpha-test>
#include <texture>

in vec4 v_color;
in vec2 v_uv0;
uniform sampler2D texture;
uniform sampler2D texture2;

void main () {
vec3 color = vec3(0.);

// 获取时间,放慢速度
float t = cc_time.x * 0.6;

// 从噪声纹理获取偏移值
vec2 noiseUV = v_uv0 + t * vec2(0.5, 0.1);
vec2 off1 = texture2D(texture2, noiseUV).xy;

// 缩放偏移值
off1 *= 0.01;

// 采样主纹理,应用偏移
color += texture2D(texture, off1 + v_uv0).xyz;

// 垂直渐变
color *= smoothstep(-0.5, 1.3, v_uv0.y) - 0.3;

gl_FragColor = vec4(color, 1.);
}
}%

使用说明

  1. 创建材质并绑定此Shader
  2. 设置texture2为噪声纹理(Perlin噪声图)
  3. 噪声图的WrapMode设置为Repeat

水面倒影效果

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
// WaterReflection.effect
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
depthStencilState:
depthTest: false
depthWrite: false
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
rasterizerState:
cullMode: none
properties:
texture: { value: white }
texture2: { value: white }
alphaThreshold: { value: 0.5 }
}%

CCProgram vs %{
precision highp float;

#include <cc-global>
#include <cc-local>

in vec3 a_position;
in vec4 a_color;
out vec4 v_color;
in vec2 a_uv0;
out vec2 v_uv0;

void main () {
vec4 pos = vec4(a_position, 1);

#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif

v_uv0 = a_uv0;
v_color = a_color;
gl_Position = pos;
}
}%

CCProgram fs %{
precision highp float;

#include <cc-global>
#include <alpha-test>
#include <texture>

in vec4 v_color;
in vec2 v_uv0;
uniform sampler2D texture;
uniform sampler2D texture2;

void main () {
vec3 color = vec3(0.);
float t = cc_time.x * 0.6;

// UV偏移
vec2 noiseUV = v_uv0 + t * vec2(0.5, 0.1);
vec2 off1 = texture2D(texture2, noiseUV).xy;
off1 *= 0.01;

// 采样倒影
color += texture2D(texture, off1 + v_uv0).xyz;

// 应用垂直渐变和透明度
float alpha = smoothstep(0.0, 0.5, v_uv0.y);
color *= 1.0 - smoothstep(-0.5, 1.3, v_uv0.y) - 0.3;

gl_FragColor = vec4(color, alpha * 0.5);
}
}%

TypeScript实现(结合RenderTexture)

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
const { ccclass, property } = cc._decorator;

@ccclass
export default class WaterReflection extends cc.Component {
@property(cc.Camera)
camera: cc.Camera = null;

@property(cc.Node)
reflectionNode: cc.Node = null;

private texture: cc.RenderTexture = null;

onLoad() {
if (!this.texture) {
this.texture = new cc.RenderTexture();
this.texture.initWithSize(
this.reflectionNode.width,
this.reflectionNode.height,
cc.gfx.RB_FMT_S8
);
this.camera.targetTexture = this.texture;
}
}

updateTexture(): cc.RenderTexture {
if (!this.texture) return null;
this.camera.render();
return this.texture;
}

update(dt: number) {
const tex = this.updateTexture();
if (tex) {
const sprite = this.reflectionNode.getComponent(cc.Sprite);
if (!sprite.spriteFrame) {
sprite.spriteFrame = new cc.SpriteFrame();
}
sprite.spriteFrame.setTexture(tex);
}
}
}

高级技巧

多重纹理混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
vec4 tex1 = texture2D(texture, v_uv0);
vec4 tex2 = texture2D(texture2, v_uv0 * 2.0);

// 混合模式:叠加
vec4 result = tex1 + tex2 * 0.5;

// 混合模式:相乘
vec4 result = tex1 * tex2;

// 混合模式:插值
vec4 result = mix(tex1, tex2, 0.5);

gl_FragColor = result;
}

UV动画

1
2
3
4
5
6
7
8
uniform float u_speed;
uniform vec2 u_direction;

void main() {
vec2 animatedUV = v_uv0 + cc_time.x * u_speed * u_direction;
vec4 color = texture2D(texture, animatedUV);
gl_FragColor = color;
}

边缘发光

1
2
3
4
5
6
// 基于法线的边缘发光
vec3 viewDir = normalize(cc_cameraPos.xyz - worldPos.xyz);
float rim = 1.0 - max(dot(viewDir, normal), 0.0);
rim = pow(rim, 3.0);
vec3 rimColor = vec3(0.0, 0.5, 1.0) * rim;
gl_FragColor += vec4(rimColor, 0.0);

性能优化

精度选择

1
2
3
4
5
6
// 顶点着色器:使用highp
precision highp float;

// 片段着色器:根据需要选择
precision mediump float; // 移动端推荐
// precision highp float; // PC端或需要高精度

避免动态分支

1
2
3
4
5
6
7
8
9
10
11
12
// 不推荐:复杂的if分支
if (condition1) {
// ...
} else if (condition2) {
// ...
} else {
// ...
}

// 推荐:使用step或mix
float factor = step(threshold, value);
vec4 result = mix(colorA, colorB, factor);

减少纹理采样

1
2
3
4
5
6
7
8
9
10
11
// 不推荐:多次采样同一纹理
vec4 c1 = texture2D(tex, uv);
vec4 c2 = texture2D(tex, uv + offset);
vec4 c3 = texture2D(tex, uv - offset);

// 推荐:预计算UV偏移
vec2 offsets[3] = vec2[](vec2(0.0), vec2(0.01, 0.0), vec2(-0.01, 0.0));
vec4 colors[3];
for (int i = 0; i < 3; i++) {
colors[i] = texture2D(tex, uv + offsets[i]);
}

调试技巧

可视化调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 输出UV坐标
void main() {
gl_FragColor = vec4(v_uv0, 0.0, 1.0);
}

// 输出法线
void main() {
gl_FragColor = vec4(normal * 0.5 + 0.5, 1.0);
}

// 输出深度
void main() {
gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}

这些都是实际项目中用过的Shader效果,GLSL语法和内置函数是基础,特效实现需要多调试。闪白效果用得最多,水波纹和倒影适合做场景氛围。

参考