第9.1章:2D渐变精灵着色器

在2D游戏开发中,视觉效果是创造吸引力的重要手段。本章将详细介绍如何在Cocos Creator中实现各种2D精灵特效,包括线性渐变、径向渐变和动态渐变。

🎯 学习目标

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

  • 2D渐变的数学原理和实现方法
  • 在Cocos Creator中创建各种类型的渐变着色器
  • 各种渐变类型的实现(线性、径向、角度渐变)
  • 动态渐变和动画效果控制

💡 渐变效果原理

渐变的基本类型

2D渐变主要包含以下几种类型:

  1. 线性渐变(Linear Gradient)

    • 沿直线方向的颜色过渡
    • 可控制方向和位置
  2. 径向渐变(Radial Gradient)

    • 从中心点向外扩散的圆形渐变
    • 可控制中心点和半径
  3. 角度渐变(Angular Gradient)

    • 围绕中心点旋转的渐变
    • 适合制作特效
  4. 动态渐变(Animated Gradient)

    • 随时间变化的渐变效果
    • 增强游戏视觉表现力

📐 渐变数学原理

线性渐变计算

1
2
3
4
5
6
7
8
9
10
11
// 线性渐变的基础计算
float linearGradient(vec2 uv, vec2 direction, vec2 offset) {
// 标准化渐变方向
vec2 normalizedDir = normalize(direction);

// 计算UV在渐变方向上的投影
float projection = dot(uv - offset, normalizedDir);

// 将投影映射到[0,1]范围
return clamp(projection, 0.0, 1.0);
}

径向渐变计算

1
2
3
4
5
6
7
8
// 径向渐变的基础计算
float radialGradient(vec2 uv, vec2 center, float radius) {
// 计算到中心点的距离
float distance = length(uv - center);

// 标准化到[0,1]范围
return clamp(1.0 - distance / radius, 0.0, 1.0);
}

角度渐变计算

1
2
3
4
5
6
7
8
9
10
11
12
// 角度渐变的基础计算
float angularGradient(vec2 uv, vec2 center, float rotation) {
// 计算到中心点的角度
vec2 dir = uv - center;
float angle = atan(dir.y, dir.x);

// 旋转偏移
angle += rotation;

// 标准化到[0,1]范围
return (angle + PI) / (2.0 * PI);
}

🔧 基础线性渐变着色器

简单线性渐变实现

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
CCEffect:
techniques:
- name: opaque
passes:
- vert: sprite-vs
frag: linear-gradient-fs
properties:
mainTexture: { value: white }
startColor: { value: [1, 0, 0, 1], editor: { type: color } }
endColor: { value: [0, 0, 1, 1], editor: { type: color } }
gradientDir: { value: [1, 0], editor: { tooltip: "渐变方向" } }
gradientOffset: { value: [0, 0], editor: { tooltip: "渐变偏移" } }
gradientPower: { value: 1.0, range: [0.1, 5.0], editor: { tooltip: "渐变强度" } }

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

in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;

out vec2 v_uv;
out vec4 v_color;

vec4 vert() {
vec4 pos = vec4(a_position, 1);
pos = cc_matWorld * pos;
pos = cc_matViewProj * pos;

v_uv = a_texCoord;
v_color = a_color;

return pos;
}
}%

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

in vec2 v_uv;
in vec4 v_color;

uniform sampler2D mainTexture;

uniform GradientUniforms {
vec4 startColor;
vec4 endColor;
vec2 gradientDir;
vec2 gradientOffset;
};

vec4 frag() {
// 采样原始纹理
vec4 texColor = texture(mainTexture, v_uv);

// 计算渐变因子
vec2 normalizedDir = normalize(gradientDir);
float gradientFactor = dot(v_uv - gradientOffset, normalizedDir);
gradientFactor = clamp(gradientFactor, 0.0, 1.0);

// 在起始颜色之间插值
vec4 gradientColor = mix(startColor, endColor, gradientFactor);

// 与原始纹理混合
vec4 finalColor = texColor * gradientColor * v_color;

return finalColor;
}
}%

多色线性渐变

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
CCProgram multi-color-gradient-fs %{
precision highp float;

uniform MultiGradientUniforms {
vec4 color1;
vec4 color2;
vec4 color3;
vec4 color4;
vec2 gradientDir;
float segments; // 分段数
};

vec4 multiColorGradient(float factor) {
// 将[0,1]分成segments段
float segmentSize = 1.0 / segments;
float segment = floor(factor / segmentSize);
float localFactor = (factor - segment * segmentSize) / segmentSize;

// 根据段选择颜色
if (segment < 1.0) {
return mix(color1, color2, localFactor);
} else if (segment < 2.0) {
return mix(color2, color3, localFactor);
} else {
return mix(color3, color4, localFactor);
}
}

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

vec2 normalizedDir = normalize(gradientDir);
float gradientFactor = dot(v_uv, normalizedDir);
gradientFactor = clamp(gradientFactor, 0.0, 1.0);

vec4 gradientColor = multiColorGradient(gradientFactor);

return texColor * gradientColor * v_color;
}
}%

?? 径向渐变着色器

基础径向渐变

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 radial-gradient-fs %{
precision highp float;

uniform RadialGradientUniforms {
vec4 centerColor;
vec4 edgeColor;
vec2 center; // 中心坐标 [0-1]
float radius; // 半径 [0-1]
float smoothness; // 平滑度
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 计算到中心的距离
float distance = length(v_uv - center);

// 标准化距离
float normalizedDistance = clamp(distance / radius, 0.0, 1.0);

// 应用平滑度
normalizedDistance = smoothstep(0.0, smoothness, normalizedDistance);

// 颜色插值
vec4 gradientColor = mix(centerColor, edgeColor, normalizedDistance);

return texColor * gradientColor * v_color;
}
}%

圆形径向渐变

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
CCProgram elliptical-gradient-fs %{
uniform EllipticalUniforms {
vec4 centerColor;
vec4 edgeColor;
vec2 center;
vec2 radius; // x和y方向的半径
float rotation; // 旋转角度
};

// 旋转矩阵
mat2 rotate2D(float angle) {
float s = sin(angle);
float c = cos(angle);
return mat2(c, -s, s, c);
}

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 转换到旋转后的坐标
vec2 pos = v_uv - center;

// 应用旋转
pos = rotate2D(rotation) * pos;

// 椭圆距离
vec2 normalizedPos = pos / radius;
float distance = length(normalizedPos);

float gradientFactor = clamp(distance, 0.0, 1.0);
vec4 gradientColor = mix(centerColor, edgeColor, gradientFactor);

return texColor * gradientColor * v_color;
}
}%

?? 角度渐变着色器

单色角度渐变

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
CCProgram angular-gradient-fs %{
precision highp float;

uniform AngularUniforms {
vec2 center;
float rotation;
float brightness;
float saturation;
};

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

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 计算角度
vec2 dir = v_uv - center;
float angle = atan(dir.y, dir.x);

// 应用旋转偏移
angle += rotation;
float hue = (angle + PI) / (2.0 * PI);

// 应用HSV颜色
vec3 hsv = vec3(hue, saturation, brightness);
vec3 gradientColor = hsv2rgb(hsv);

return texColor * vec4(gradientColor, 1.0) * v_color;
}
}%

双色角度渐变

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 dual-angular-gradient-fs %{
uniform DualAngularUniforms {
vec4 color1;
vec4 color2;
vec2 center;
float rotation;
float sharpness; // 锐化程度
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

vec2 dir = v_uv - center;
float angle = atan(dir.y, dir.x) + rotation;

// 角度映射到[0,1]然后使用sin函数放大以增强效果
float factor = sin(angle) * 0.5 + 0.5;

// 应用颜色
factor = pow(factor, sharpness);

vec4 gradientColor = mix(color1, color2, factor);

return texColor * gradientColor * v_color;
}
}%

? 静态效果效果

时间效果效果

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 flowing-gradient-fs %{
precision highp float;
#include <builtin/uniforms/cc-global>

uniform FlowingUniforms {
vec4 color1;
vec4 color2;
vec2 flowDirection;
float flowSpeed;
float waveLength;
float intensity;
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 时间驱动的动画
vec2 flow = flowDirection * cc_time.x * flowSpeed;
float wave = sin((dot(v_uv, normalize(flowDirection)) + flow.x) * waveLength);

// 基础效果
float factor = (wave + 1.0) * 0.5;
factor = mix(0.5, factor, intensity);

vec4 gradientColor = mix(color1, color2, factor);

return texColor * gradientColor * v_color;
}
}%

脉冲效果效果

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 pulse-gradient-fs %{
uniform PulseUniforms {
vec4 baseColor;
vec4 pulseColor;
vec2 center;
float pulseSpeed;
float pulseRadius;
float pulseWidth;
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 计算到中心的距离
float distance = length(v_uv - center);

// 应用脉冲效果
float pulse = sin(cc_time.x * pulseSpeed - distance * pulseRadius);
pulse = smoothstep(-pulseWidth, pulseWidth, pulse);

// 应用颜色
vec4 gradientColor = mix(baseColor, pulseColor, pulse);

return texColor * gradientColor * v_color;
}
}%

?? 实际应用效果

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
CCProgram energy-bar-fs %{
uniform EnergyBarUniforms {
vec4 emptyColor; // 空状态颜色
vec4 fullColor; // 满状态颜色
vec4 criticalColor; // 危险状态颜色
float fillAmount; // 填充量 [0-1]
float criticalThreshold; // 危险阈值
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 判断是否填充
bool isFilled = v_uv.x <= fillAmount;

vec4 gradientColor;
if (isFilled) {
// 满状态
if (fillAmount <= criticalThreshold) {
// 危险状态 - 闪烁颜色
float flash = sin(cc_time.x * 10.0) * 0.5 + 0.5;
gradientColor = mix(criticalColor, fullColor, flash);
} else {
// 正常状态 - 渐变颜色
float localFactor = v_uv.x / fillAmount;
gradientColor = mix(fullColor, emptyColor, localFactor * 0.3);
}
} else {
// 空状态
gradientColor = emptyColor;
}

return texColor * gradientColor * v_color;
}
}%

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
CCProgram button-glow-fs %{
uniform GlowUniforms {
vec4 baseColor;
vec4 glowColor;
float glowIntensity;
float glowRadius;
vec2 glowCenter;
};

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 计算到按钮中心的距离
float distance = length(v_uv - glowCenter);

// 应用发光效果
float glow = exp(-distance * glowRadius) * glowIntensity;
glow = clamp(glow, 0.0, 1.0);

// 混合基础颜色和发光颜色
vec4 finalColor = mix(baseColor, glowColor, glow);
finalColor += glowColor * glow * 0.5; // 添加额外发光

return texColor * finalColor * v_color;
}
}%

?? 优化效果

1. 预先优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 预先计算渐变因子
// 在顶点着色器中计算
out float v_gradientFactor;

void vert() {
// 在顶点着色器中计算渐变因子
vec2 normalizedDir = normalize(gradientDir);
v_gradientFactor = dot(a_texCoord - gradientOffset, normalizedDir);
// ... 其他计算
}

// 片元着色器中使用
vec4 frag() {
float factor = clamp(v_gradientFactor, 0.0, 1.0);
vec4 gradientColor = mix(startColor, endColor, factor);
// ... 其他计算
}

2. 使用LUT优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用1D查找表为片元着色器提供颜色
uniform sampler2D gradientLUT;

vec4 frag() {
vec4 texColor = texture(mainTexture, v_uv);

// 计算渐变因子
float factor = computeGradientFactor(v_uv);

// 使用LUT获取颜色
vec4 gradientColor = texture(gradientLUT, vec2(factor, 0.5));

return texColor * gradientColor * v_color;
}

?? 本章总结

通过本章学习,我们掌握了:

  • ? 2D渐变的数学原理和实现
  • ? 线性、径向、角度渐变技术
  • ? 动态渐变和动画效果
  • ? 完整的TypeScript控制系统

?? 下一步学习

掌握2D渐变精灵着色器后,建议继续学习:
👉 第9.2章:2D波纹扭曲着色器

?? 实践练习

  1. 创建练习: 创建一个UI按钮的渐变效果系统
  2. 创建练习: 实现血条的动态渐变显示
  3. 应用练习: 开发技能图标的闪烁渐变特效

系列导航