第8.3章:溶解特效Surface Shader

溶解特效是游戏中极具视觉冲击力的效果之一,常用于角色死亡、物体消失、传送门等场景。本章将详细介绍如何使用Surface Shader实现各种溶解效果。

🎯 学习目标

  • 理解溶解效果的实现原理
  • 掌握基于噪声纹理的溶解技术
  • 学会创建高级溶解变体效果
  • 理解溶解边缘的光效处理

💡 溶解效果原理

基础原理

溶解效果通过噪声纹理控制像素的显示与隐藏,结合阈值判断实现渐进式消失。

1
2
3
4
5
// 基础溶解逻辑
float noise = texture(dissolveTexture, uv).r;
if (noise < dissolveAmount) {
discard; // 丢弃像素
}

视觉效果分析

1
2
3
完整显示 → 边缘燃烧 → 逐渐消失 → 完全透明
↓ ↓ ↓ ↓
noise>threshold → edge glow → discard → invisible

🔧 基础溶解效果

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
CCEffect %{
techniques:
- name: basic-dissolve
passes:
- vert: dissolve-vs:vert
frag: dissolve-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
edgeWidth: { value: 0.1, range: [0.01, 0.5] }
edgeColor: { value: [1, 0.5, 0, 1], editor: { type: color } }
}%

CCProgram dissolve-vs %{
#include <surface-vertex>
}%

CCProgram dissolve-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform float edgeWidth;
uniform vec4 edgeColor;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
// 基础颜色采样
vec4 albedo = texture(mainTexture, In.uv) * baseColor;

// 噪声纹理采样
float noise = texture(dissolveTexture, In.uv).r;

// 溶解判断
if (noise < dissolveAmount) {
discard;
}

// 边缘发光效果
float edge = smoothstep(dissolveAmount, dissolveAmount + edgeWidth, noise);
vec3 emissive = (1.0 - edge) * edgeColor.rgb;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = emissive;
Out.ao = 1.0;
}
}%

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
CCEffect %{
techniques:
- name: dual-dissolve
passes:
- vert: dual-dissolve-vs:vert
frag: dual-dissolve-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
innerEdgeWidth: { value: 0.05, range: [0.01, 0.2] }
outerEdgeWidth: { value: 0.15, range: [0.05, 0.5] }
innerEdgeColor: { value: [1, 1, 0, 1], editor: { type: color } }
outerEdgeColor: { value: [1, 0, 0, 1], editor: { type: color } }
edgeIntensity: { value: 2.0, range: [1, 10] }
}%

CCProgram dual-dissolve-vs %{
#include <surface-vertex>
}%

CCProgram dual-dissolve-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform float innerEdgeWidth;
uniform float outerEdgeWidth;
uniform vec4 innerEdgeColor;
uniform vec4 outerEdgeColor;
uniform float edgeIntensity;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
vec4 albedo = texture(mainTexture, In.uv) * baseColor;
float noise = texture(dissolveTexture, In.uv).r;

// 溶解判断
if (noise < dissolveAmount) {
discard;
}

// 内层边缘(热核心)
float innerEdge = 1.0 - smoothstep(dissolveAmount,
dissolveAmount + innerEdgeWidth,
noise);

// 外层边缘(火焰边缘)
float outerEdge = 1.0 - smoothstep(dissolveAmount,
dissolveAmount + outerEdgeWidth,
noise);

// 边缘颜色混合
vec3 innerGlow = innerEdge * innerEdgeColor.rgb * edgeIntensity;
vec3 outerGlow = (outerEdge - innerEdge) * outerEdgeColor.rgb * (edgeIntensity * 0.7);

vec3 totalEmissive = innerGlow + outerGlow;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = totalEmissive;
Out.ao = 1.0;
}
}%

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
68
69
70
71
72
73
74
75
76
77
78
CCEffect %{
techniques:
- name: noise-dissolve
passes:
- vert: noise-dissolve-vs:vert
frag: noise-dissolve-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
noiseScale: { value: 1.0, range: [0.1, 10] }
noiseStrength: { value: 0.5, range: [0, 1] }
edgeWidth: { value: 0.1, range: [0.01, 0.5] }
edgeColor: { value: [1, 0.5, 0, 1], editor: { type: color } }
animationSpeed: { value: 1.0, range: [0, 5] }
}%

CCProgram noise-dissolve-vs %{
#include <surface-vertex>
}%

CCProgram noise-dissolve-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform float noiseScale;
uniform float noiseStrength;
uniform float edgeWidth;
uniform vec4 edgeColor;
uniform float animationSpeed;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
vec4 albedo = texture(mainTexture, In.uv) * baseColor;

// 动态UV偏移
vec2 animatedUV = In.uv + sin(cc_time.x * animationSpeed) * 0.02;

// 多层噪声采样
float noise1 = texture(dissolveTexture, animatedUV * noiseScale).r;
float noise2 = texture(dissolveTexture, animatedUV * noiseScale * 2.0 + 0.5).g;

// 噪声混合
float finalNoise = mix(noise1, noise2, noiseStrength);

// 添加程序化噪声
float proceduralNoise = random(In.uv + cc_time.x * 0.1) * 0.1;
finalNoise += proceduralNoise;

// 溶解判断
if (finalNoise < dissolveAmount) {
discard;
}

// 边缘效果
float edge = 1.0 - smoothstep(dissolveAmount,
dissolveAmount + edgeWidth,
finalNoise);

// 边缘闪烁效果
float flicker = sin(cc_time.x * 20.0 + finalNoise * 10.0) * 0.3 + 0.7;
vec3 emissive = edge * edgeColor.rgb * flicker;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = emissive;
Out.ao = 1.0;
}

float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
}%

🎨 高级溶解特效

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
CCEffect %{
techniques:
- name: portal-dissolve
passes:
- vert: portal-vs:vert
frag: portal-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
portalColor: { value: [0, 0.5, 1, 1], editor: { type: color } }
energyColor: { value: [0, 1, 1, 1], editor: { type: color } }
spiralSpeed: { value: 2.0, range: [0.1, 10] }
spiralTightness: { value: 5.0, range: [1, 20] }
energyIntensity: { value: 3.0, range: [1, 10] }
distortionStrength: { value: 0.1, range: [0, 0.5] }
}%

CCProgram portal-vs %{
#include <surface-vertex>
}%

CCProgram portal-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform vec4 portalColor;
uniform vec4 energyColor;
uniform float spiralSpeed;
uniform float spiralTightness;
uniform float energyIntensity;
uniform float distortionStrength;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
vec2 uv = In.uv;

// 径向坐标转换
vec2 center = vec2(0.5, 0.5);
vec2 toCenter = uv - center;
float distance = length(toCenter);
float angle = atan(toCenter.y, toCenter.x);

// 螺旋扭曲
float spiral = angle + distance * spiralTightness + cc_time.x * spiralSpeed;
vec2 spiralUV = uv + vec2(cos(spiral), sin(spiral)) * distortionStrength * distance;

vec4 albedo = texture(mainTexture, spiralUV) * baseColor;

// 溶解噪声采样
float noise = texture(dissolveTexture, spiralUV).r;

// 径向渐变
float radialGradient = 1.0 - distance * 2.0;
noise = mix(noise, radialGradient, 0.3);

if (noise < dissolveAmount) {
discard;
}

// 传送门能量效果
float portalEnergy = sin(spiral * 2.0) * 0.5 + 0.5;
portalEnergy *= smoothstep(0.0, 0.3, distance) * smoothstep(0.8, 0.0, distance);

// 边缘发光
float edgeFactor = 1.0 - smoothstep(dissolveAmount, dissolveAmount + 0.1, noise);

vec3 finalEmissive = portalEnergy * portalColor.rgb +
edgeFactor * energyColor.rgb * energyIntensity;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = finalEmissive;
Out.ao = 1.0;
}
}%

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
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
CCEffect %{
techniques:
- name: electric-dissolve
passes:
- vert: electric-vs:vert
frag: electric-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
electricColor: { value: [0, 0.5, 1, 1], editor: { type: color } }
sparkColor: { value: [1, 1, 1, 1], editor: { type: color } }
electricFreq: { value: 10.0, range: [1, 50] }
sparkIntensity: { value: 5.0, range: [1, 20] }
edgeWidth: { value: 0.08, range: [0.01, 0.3] }
turbulence: { value: 0.3, range: [0, 1] }
}%

CCProgram electric-vs %{
#include <surface-vertex>
}%

CCProgram electric-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform vec4 electricColor;
uniform vec4 sparkColor;
uniform float electricFreq;
uniform float sparkIntensity;
uniform float edgeWidth;
uniform float turbulence;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
vec4 albedo = texture(mainTexture, In.uv) * baseColor;

// 噪声采样
float noise = texture(dissolveTexture, In.uv).r;

// 添加湍流扰动
vec2 turbulentUV = In.uv + sin(cc_time.x * 3.0 + In.uv * 20.0) * turbulence * 0.01;
float turbulentNoise = texture(dissolveTexture, turbulentUV).g;
noise = mix(noise, turbulentNoise, 0.3);

if (noise < dissolveAmount) {
discard;
}

// 电流边缘效果
float edgeFactor = 1.0 - smoothstep(dissolveAmount,
dissolveAmount + edgeWidth,
noise);

// 电流闪烁
float electricPulse = sin(cc_time.x * electricFreq + noise * 30.0) * 0.5 + 0.5;
electricPulse = pow(electricPulse, 3.0); // 尖锐的脉冲
// 火花效果
float spark = 0.0;
if (edgeFactor > 0.8) {
float sparkNoise = random(In.uv + floor(cc_time.x * 10.0) * 0.1);
if (sparkNoise > 0.7) {
spark = pow(sparkNoise, 2.0) * sparkIntensity;
}
}

// 最终发光效果
vec3 electricGlow = edgeFactor * electricColor.rgb * electricPulse * 2.0;
vec3 sparkGlow = spark * sparkColor.rgb;
vec3 finalEmissive = electricGlow + sparkGlow;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = finalEmissive;
Out.ao = 1.0;
}

float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
}%

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
CCEffect %{
techniques:
- name: liquid-dissolve
passes:
- vert: liquid-vs:vert
frag: liquid-fs:frag
properties: &props
mainTexture: { value: white }
baseColor: { value: [1, 1, 1, 1], editor: { type: color } }
dissolveTexture: { value: white }
dissolveAmount: { value: 0.0, range: [0, 1] }
liquidColor: { value: [0, 1, 0.5, 1], editor: { type: color } }
bubbleColor: { value: [0.5, 1, 0.8, 1], editor: { type: color } }
flowSpeed: { value: 1.0, range: [0.1, 5] }
viscosity: { value: 0.5, range: [0.1, 2] }
bubbleSize: { value: 0.1, range: [0.01, 0.5] }
edgeThickness: { value: 0.12, range: [0.01, 0.5] }
}%

CCProgram liquid-vs %{
#include <surface-vertex>
}%

CCProgram liquid-fs %{
#include <surface-fragment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;
uniform sampler2D dissolveTexture;
uniform float dissolveAmount;
uniform vec4 liquidColor;
uniform vec4 bubbleColor;
uniform float flowSpeed;
uniform float viscosity;
uniform float bubbleSize;
uniform float edgeThickness;

void surf(in SurfaceIn In, inout SurfaceOut Out) {
// 液体流动UV
vec2 flowUV = In.uv + vec2(sin(cc_time.x * flowSpeed),
cos(cc_time.x * flowSpeed * 0.7)) * viscosity * 0.01;

vec4 albedo = texture(mainTexture, flowUV) * baseColor;

// 噪声采样
float noise = texture(dissolveTexture, flowUV).r;

// 垂直渐变模拟重力
float gravity = (1.0 - In.uv.y) * 0.3;
noise += gravity;

if (noise < dissolveAmount) {
discard;
}

// 液体边缘
float liquidEdge = 1.0 - smoothstep(dissolveAmount,
dissolveAmount + edgeThickness,
noise);

// 气泡效果
vec2 bubbleUV = In.uv * (1.0 / bubbleSize) + cc_time.x * flowSpeed * 0.1;
float bubble1 = sin(bubbleUV.x * 6.28) * sin(bubbleUV.y * 6.28);
float bubble2 = sin(bubbleUV.x * 8.0 + 1.0) * sin(bubbleUV.y * 8.0 + 1.0);
float bubblePattern = max(bubble1, bubble2 * 0.7);
bubblePattern = smoothstep(0.3, 0.8, bubblePattern);

// 液体光泽
float liquidGloss = smoothstep(0.4, 0.9, noise) * liquidEdge;

// 最终效果合成
vec3 liquidGlow = liquidEdge * liquidColor.rgb;
vec3 bubbleGlow = bubblePattern * liquidEdge * bubbleColor.rgb * 0.5;
vec3 glossGlow = liquidGloss * vec3(1.0) * 0.3;

vec3 finalEmissive = liquidGlow + bubbleGlow + glossGlow;

Out.albedo = albedo;
Out.normal = normalize(In.worldNormal);
Out.metallic = 0.0;
Out.roughness = 0.5;
Out.emissive = finalEmissive;
Out.ao = 1.0;
}
}%

🔧 TypeScript控制系统

溶解效果控制器

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { Component, Material, Vec4, _decorator } from 'cc';

const { ccclass, property, menu } = _decorator;

@ccclass('DissolveController')
@menu('Custom/DissolveController')
export class DissolveController extends Component {

@property({ range: [0, 1], displayName: '溶解进度' })
public dissolveAmount: number = 0.0;

@property({ range: [0.01, 0.5], displayName: '边缘宽度' })
public edgeWidth: number = 0.1;

@property({ type: Vec4, displayName: '边缘颜色' })
public edgeColor: Vec4 = new Vec4(1, 0.5, 0, 1);

@property({ range: [1, 10], displayName: '边缘强度' })
public edgeIntensity: number = 2.0;

@property({ displayName: '自动播放' })
public autoPlay: boolean = false;

@property({ range: [0.1, 5], displayName: '播放速度' })
public playSpeed: number = 1.0;

@property({ displayName: '循环播放' })
public loop: boolean = false;

private _material: Material | null = null;
private _isPlaying: boolean = false;
private _playDirection: number = 1; // 1为正向,-1为反向
onLoad() {
const renderer = this.getComponent('cc.MeshRenderer');
if (renderer && renderer.material) {
this._material = renderer.material;
this.updateMaterial();
}

if (this.autoPlay) {
this.play();
}
}

update(deltaTime: number) {
if (this._isPlaying && this._material) {
this.dissolveAmount += deltaTime * this.playSpeed * this._playDirection;

// 边界检查和循环处理
if (this.dissolveAmount >= 1.0) {
if (this.loop) {
this.dissolveAmount = 1.0;
this._playDirection = -1;
} else {
this.dissolveAmount = 1.0;
this._isPlaying = false;
}
} else if (this.dissolveAmount <= 0.0) {
if (this.loop) {
this.dissolveAmount = 0.0;
this._playDirection = 1;
} else {
this.dissolveAmount = 0.0;
this._isPlaying = false;
}
}

this.updateMaterial();
}
}

private updateMaterial() {
if (!this._material) return;

this._material.setProperty('dissolveAmount', this.dissolveAmount);
this._material.setProperty('edgeWidth', this.edgeWidth);
this._material.setProperty('edgeColor', this.edgeColor);
this._material.setProperty('edgeIntensity', this.edgeIntensity);
}

// 公共接口
public play() {
this._isPlaying = true;
this._playDirection = 1;
}

public reverse() {
this._isPlaying = true;
this._playDirection = -1;
}

public stop() {
this._isPlaying = false;
}

public reset() {
this.dissolveAmount = 0.0;
this._isPlaying = false;
this.updateMaterial();
}

public setDissolveAmount(amount: number) {
this.dissolveAmount = Math.max(0, Math.min(1, amount));
this.updateMaterial();
}

// 高级控制
public dissolveIn(duration: number) {
this.stop();
this.dissolveAmount = 0.0;
this.playSpeed = 1.0 / duration;
this.play();
}

public dissolveOut(duration: number) {
this.stop();
this.dissolveAmount = 1.0;
this.playSpeed = 1.0 / duration;
this.reverse();
}
}

📖 本章总结

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

  • ✅ 溶解效果的基础实现原理
  • ✅ 多种溶解变体(双层、电流、液体、传送门)
  • ✅ 噪声纹理的高级应用技巧
  • ✅ TypeScript控制系统的完整实现
  • ✅ 边缘光效的精细化处理

🚀 下一步学习

掌握了溶解特效Surface Shader后,建议继续学习:
👉 第8.4章:水面Surface Shader

💡 实践练习

  1. 创建一个魔法消失的溶解效果
  2. 实现角色死亡的灰烬溶解
  3. 开发传送门的时空扭曲溶解

系列导航