第10.4章:3D顶点动画着色器

顶点动画着色器通过在GPU上直接操作顶点位置来创建复杂的形变和动画效果,是实现高性能动态特效的核心技术。本章将深入探讨各种顶点动画技术的实现。

🎯 学习目标

  • 理解顶点动画的基本原理- 掌握波浪、爆炸、生长等动画效果
  • 学会使用噪声和数学函数控制顶点- 理解GPU顶点处理的性能优化

💡 顶点动画原理

顶点变换基础

顶点动画通过修改顶点位置实现形变:

1
2
3
4
// 基础顶点变换
vec3 originalPos = a_position;
vec3 animatedPos = originalPos + displacement;
vec4 worldPos = cc_matWorld * vec4(animatedPos, 1.0);

动画控制方式

1
2
时间输入 �?数学函数 �?位移计算 �?顶点变换
�? �? �? �? time sin/cos offset vertex

🔧 基础顶点动画

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
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
CCEffect %{
techniques:
- name: wave-displacement
passes:
- vert: wave-vs:vert
frag: wave-fs:frag
properties: &props
mainTexture: { value: white }
waveAmplitude: { value: 0.5, range: [0.0, 3.0] }
waveFrequency: { value: 2.0, range: [0.1, 10.0] }
waveSpeed: { value: 3.0, range: [0.0, 10.0] }
waveDirection: { value: [1.0, 0.0, 0.0], editor: { type: vec3 } }
secondaryWave: { value: 0.3, range: [0.0, 1.0] }
verticalOffset: { value: 1.0, range: [0.0, 3.0] }
noiseScale: { value: 1.0, range: [0.1, 5.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

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

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

out vec3 v_worldPos;
out vec3 v_worldNormal;
out vec2 v_uv;
out float v_waveHeight;

uniform float waveAmplitude;
uniform float waveFrequency;
uniform float waveSpeed;
uniform vec3 waveDirection;
uniform float secondaryWave;
uniform float verticalOffset;
uniform float noiseScale;

// 简单噪声函�? float noise(vec3 pos) {
return fract(sin(dot(pos, vec3(12.9898, 78.233, 45.164))) * 43758.5453);
}

vec4 vert() {
vec3 worldPos = (cc_matWorld * vec4(a_position, 1)).xyz;

// 主波浪计�? float wavePhase = dot(a_position, normalize(waveDirection)) * waveFrequency;
float mainWave = sin(wavePhase + cc_time.x * waveSpeed) * waveAmplitude;

// 次要波浪(垂直方向)
float secondWave = sin(a_position.y * waveFrequency * 2.0 + cc_time.x * waveSpeed * 1.5)
* waveAmplitude * secondaryWave;

// 噪声扰动
vec3 noisePos = a_position * noiseScale + cc_time.x * 0.5;
float noiseOffset = (noise(noisePos) - 0.5) * waveAmplitude * 0.3;

// 合成位移
float totalDisplacement = mainWave + secondWave + noiseOffset;

// 垂直偏移
vec3 animatedPos = a_position;
animatedPos.y += totalDisplacement * verticalOffset;

// 法线计算(简化)
vec3 tangent = normalize(waveDirection);
float gradient = cos(wavePhase + cc_time.x * waveSpeed) * waveAmplitude * waveFrequency;
vec3 animatedNormal = normalize(a_normal + tangent * gradient * 0.1);

v_worldPos = (cc_matWorld * vec4(animatedPos, 1)).xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(animatedNormal, 0)).xyz);
v_uv = a_texCoord;
v_waveHeight = totalDisplacement;

return cc_matViewProj * cc_matWorld * vec4(animatedPos, 1);
}
}%

CCProgram wave-fs %{
precision highp float;
#include <cc-global>
#include <cc-environment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;
in float v_waveHeight;

vec4 frag() {
vec3 normal = normalize(v_worldNormal);

// 基础纹理
vec4 albedo = texture(mainTexture, v_uv) * baseColor;

// 根据波浪高度调整颜色
float heightFactor = v_waveHeight * 0.5 + 0.5;
vec3 waveColor = mix(vec3(0.2, 0.4, 0.8), vec3(0.8, 0.9, 1.0), heightFactor);

// 简单光�? vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float NdotL = max(0.0, dot(normal, lightDir));

vec3 finalColor = albedo.rgb * waveColor * (0.3 + 0.7 * NdotL);

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

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
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
122
123
124
125
126
CCEffect %{
techniques:
- name: explosion-displacement
passes:
- vert: explosion-vs:vert
frag: explosion-fs:frag
properties: &props
mainTexture: { value: white }
explosionCenter: { value: [0.0, 0.0, 0.0], editor: { type: vec3 } }
explosionRadius: { value: 2.0, range: [0.1, 10.0] }
explosionForce: { value: 3.0, range: [0.0, 10.0] }
explosionTime: { value: 0.0, range: [0.0, 5.0] }
fragmentSize: { value: 0.1, range: [0.01, 1.0] }
randomSeed: { value: 123.456, range: [0.0, 1000.0] }
gravityEffect: { value: 1.0, range: [0.0, 3.0] }
rotationSpeed: { value: 5.0, range: [0.0, 20.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

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

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

out vec3 v_worldPos;
out vec3 v_worldNormal;
out vec2 v_uv;
out float v_explosionFactor;

uniform vec3 explosionCenter;
uniform float explosionRadius;
uniform float explosionForce;
uniform float explosionTime;
uniform float fragmentSize;
uniform float randomSeed;
uniform float gravityEffect;
uniform float rotationSpeed;

// 随机函数
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898, 78.233, 45.164))) * 43758.5453);
}

vec3 randomDirection(vec3 seed) {
float theta = random(seed) * 6.28318;
float phi = random(seed + vec3(1.0)) * 3.14159;
return vec3(sin(phi) * cos(theta), cos(phi), sin(phi) * sin(theta));
}

vec4 vert() {
vec3 worldPos = (cc_matWorld * vec4(a_position, 1)).xyz;

// 距离爆炸中心的距�? vec3 toCenter = worldPos - explosionCenter;
float distanceToCenter = length(toCenter);

// 爆炸影响因子
float explosionInfluence = 1.0 - smoothstep(0.0, explosionRadius, distanceToCenter);

// 碎片随机种子
vec3 fragmentSeed = a_position + vec3(randomSeed);

// 爆炸方向(从中心向外 + 随机偏移�? vec3 explosionDir = normalize(toCenter + randomDirection(fragmentSeed) * 0.5);

// 爆炸位移
float explosionProgress = min(explosionTime / 2.0, 1.0);
vec3 explosionOffset = explosionDir * explosionForce * explosionInfluence * explosionProgress;

// 重力效果
explosionOffset.y -= gravityEffect * explosionProgress * explosionProgress;

// 碎片大小变化
float fragmentScale = 1.0 - explosionInfluence * fragmentSize * explosionProgress;

// 旋转效果
float rotationAngle = explosionProgress * rotationSpeed * random(fragmentSeed + vec3(2.0));
vec3 rotationAxis = randomDirection(fragmentSeed + vec3(3.0));

// 简化的旋转(仅绕Y轴)
float cosAngle = cos(rotationAngle);
float sinAngle = sin(rotationAngle);
vec3 rotatedPos = a_position;
rotatedPos.x = a_position.x * cosAngle - a_position.z * sinAngle;
rotatedPos.z = a_position.x * sinAngle + a_position.z * cosAngle;

// 最终位置计�? vec3 animatedPos = rotatedPos * fragmentScale + explosionOffset;

v_worldPos = (cc_matWorld * vec4(animatedPos, 1)).xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;
v_explosionFactor = explosionInfluence * explosionProgress;

return cc_matViewProj * cc_matWorld * vec4(animatedPos, 1);
}
}%

CCProgram explosion-fs %{
precision highp float;
#include <cc-global>
#include <cc-environment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;
in float v_explosionFactor;

vec4 frag() {
// 基础纹理
vec4 albedo = texture(mainTexture, v_uv) * baseColor;

// 爆炸发光效果
vec3 explosionColor = mix(vec3(1.0), vec3(1.0, 0.3, 0.1), v_explosionFactor);

// 透明度变�? float explosionAlpha = 1.0 - v_explosionFactor * 0.8;

vec3 finalColor = albedo.rgb * explosionColor;

return vec4(finalColor, albedo.a * explosionAlpha);
}
}%

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
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
122
123
124
125
126
127
128
129
CCEffect %{
techniques:
- name: growth-animation
passes:
- vert: growth-vs:vert
frag: growth-fs:frag
properties: &props
mainTexture: { value: white }
growthProgress: { value: 0.5, range: [0.0, 1.0] }
growthOrigin: { value: [0.0, -1.0, 0.0], editor: { type: vec3 } }
growthHeight: { value: 2.0, range: [0.1, 5.0] }
twistAmount: { value: 0.0, range: [0.0, 6.28] }
scaleVariation: { value: 0.2, range: [0.0, 1.0] }
noiseScale: { value: 1.0, range: [0.1, 5.0] }
bendStrength: { value: 0.5, range: [0.0, 2.0] }
windEffect: { value: 0.3, range: [0.0, 1.0] }
baseColor: { value: [0.2, 0.8, 0.3, 1.0], editor: { type: color } }
}%

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

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

out vec3 v_worldPos;
out vec3 v_worldNormal;
out vec2 v_uv;
out float v_growthFactor;

uniform float growthProgress;
uniform vec3 growthOrigin;
uniform float growthHeight;
uniform float twistAmount;
uniform float scaleVariation;
uniform float noiseScale;
uniform float bendStrength;
uniform float windEffect;

// 噪声函数
float noise(vec3 pos) {
return fract(sin(dot(pos, vec3(12.9898, 78.233, 45.164))) * 43758.5453);
}

vec4 vert() {
vec3 pos = a_position;

// 计算相对于生长原点的高度
float relativeHeight = (pos.y - growthOrigin.y) / growthHeight;
relativeHeight = clamp(relativeHeight, 0.0, 1.0);

// 生长遮罩
float growthMask = step(relativeHeight, growthProgress);
float growthFade = 1.0 - smoothstep(growthProgress - 0.1, growthProgress, relativeHeight);

// 扭转效果
float twistAngle = twistAmount * relativeHeight * growthProgress;
float cosAngle = cos(twistAngle);
float sinAngle = sin(twistAngle);

vec3 twistedPos = pos;
twistedPos.x = pos.x * cosAngle - pos.z * sinAngle;
twistedPos.z = pos.x * sinAngle + pos.z * cosAngle;

// 缩放变化
vec3 noisePos = pos * noiseScale;
float scaleNoise = noise(noisePos) * scaleVariation;
float scaleModifier = 1.0 + scaleNoise * relativeHeight;

// 弯曲效果
float bendOffset = sin(relativeHeight * 3.14159) * bendStrength * growthProgress;
twistedPos.x += bendOffset * relativeHeight;

// 风效�? float windSway = sin(cc_time.x * 2.0 + pos.x) * windEffect * relativeHeight * growthProgress;
twistedPos.x += windSway;
twistedPos.z += windSway * 0.5;

// 应用生长效果
vec3 animatedPos = mix(growthOrigin, twistedPos, growthMask * growthFade) * scaleModifier;

// 法线调整(简化)
vec3 animatedNormal = a_normal;
if (twistAmount > 0.0) {
animatedNormal.x = a_normal.x * cosAngle - a_normal.z * sinAngle;
animatedNormal.z = a_normal.x * sinAngle + a_normal.z * cosAngle;
}

v_worldPos = (cc_matWorld * vec4(animatedPos, 1)).xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(animatedNormal, 0)).xyz);
v_uv = a_texCoord;
v_growthFactor = relativeHeight * growthProgress;

return cc_matViewProj * cc_matWorld * vec4(animatedPos, 1);
}
}%

CCProgram growth-fs %{
precision highp float;
#include <cc-global>
#include <cc-environment>

uniform sampler2D mainTexture;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;
in float v_growthFactor;

vec4 frag() {
vec3 normal = normalize(v_worldNormal);

// 基础纹理
vec4 albedo = texture(mainTexture, v_uv) * baseColor;

// 生长颜色渐变
vec3 youngColor = vec3(0.8, 1.0, 0.3); // 嫩绿�? vec3 matureColor = vec3(0.2, 0.7, 0.1); // 深绿�? vec3 growthColor = mix(youngColor, matureColor, v_growthFactor);

// 简单光�? vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float NdotL = max(0.0, dot(normal, lightDir));

vec3 finalColor = albedo.rgb * growthColor * (0.4 + 0.6 * NdotL);

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

🎨 高级顶点动画

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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
CCEffect %{
techniques:
- name: liquid-simulation
passes:
- vert: liquid-vs:vert
frag: liquid-fs:frag
properties: &props
mainTexture: { value: white }
liquidDensity: { value: 1.0, range: [0.1, 5.0] }
viscosity: { value: 0.5, range: [0.0, 2.0] }
surfaceTension: { value: 0.8, range: [0.0, 2.0] }
flowDirection: { value: [0.0, -1.0, 0.0], editor: { type: vec3 } }
turbulence: { value: 0.3, range: [0.0, 1.0] }
waveHeight: { value: 0.2, range: [0.0, 1.0] }
flowSpeed: { value: 1.0, range: [0.0, 5.0] }
liquidColor: { value: [0.2, 0.6, 1.0, 0.8], editor: { type: color } }
}%

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

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

out vec3 v_worldPos;
out vec3 v_worldNormal;
out vec2 v_uv;
out float v_liquidHeight;

uniform float liquidDensity;
uniform float viscosity;
uniform float surfaceTension;
uniform vec3 flowDirection;
uniform float turbulence;
uniform float waveHeight;
uniform float flowSpeed;

// 多层噪声
float noise(vec3 pos) {
return fract(sin(dot(pos, vec3(12.9898, 78.233, 45.164))) * 43758.5453);
}

float fbm(vec3 pos) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;

for (int i = 0; i < 4; i++) {
value += noise(pos * frequency) * amplitude;
amplitude *= 0.5;
frequency *= 2.0;
}

return value;
}

vec4 vert() {
vec3 pos = a_position;

// 时间偏移
float time = cc_time.x * flowSpeed;

// 主流�? vec3 flowOffset = flowDirection * time * viscosity;

// 表面波动
float wavePhase = dot(pos.xz, vec2(1.0, 0.5)) * 3.0 + time;
float wave1 = sin(wavePhase) * waveHeight;
float wave2 = sin(wavePhase * 1.7 + 1.3) * waveHeight * 0.5;

// 湍流
vec3 turbulencePos = pos + flowOffset + vec3(time * 0.3);
float turbulenceNoise = fbm(turbulencePos * liquidDensity) * turbulence;

// 表面张力效果
float surface = surfaceTension * sin(pos.x * 5.0 + time) * cos(pos.z * 5.0 + time * 1.3) * 0.1;

// 合成液体位移
vec3 liquidPos = pos;
liquidPos.y += wave1 + wave2 + turbulenceNoise + surface;
liquidPos += flowOffset * 0.1;

// 法线扰动计算
float dx = (sin((pos.x + 0.01) * 3.0 + time) - sin((pos.x - 0.01) * 3.0 + time)) / 0.02;
float dz = (sin((pos.z + 0.01) * 3.0 + time) - sin((pos.z - 0.01) * 3.0 + time)) / 0.02;
vec3 liquidNormal = normalize(vec3(-dx, 1.0, -dz));

v_worldPos = (cc_matWorld * vec4(liquidPos, 1)).xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(liquidNormal, 0)).xyz);
v_uv = a_texCoord + flowOffset.xz * 0.1;
v_liquidHeight = liquidPos.y - pos.y;

return cc_matViewProj * cc_matWorld * vec4(liquidPos, 1);
}
}%

CCProgram liquid-fs %{
precision highp float;
#include <cc-global>
#include <cc-environment>

uniform sampler2D mainTexture;
uniform vec4 liquidColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;
in float v_liquidHeight;

vec4 frag() {
vec3 normal = normalize(v_worldNormal);
vec3 viewDir = normalize(cc_cameraPos.xyz - v_worldPos);

// 基础纹理
vec4 albedo = texture(mainTexture, v_uv);

// 菲涅尔反射(液体特性)
float fresnel = pow(1.0 - max(0.0, dot(normal, viewDir)), 3.0);

// 液体颜色混合
vec3 liquidFinal = mix(liquidColor.rgb, albedo.rgb, 0.3);
liquidFinal += vec3(0.8, 0.9, 1.0) * fresnel * 0.5;

// 根据高度变化调整透明度和颜色
float heightFactor = clamp(v_liquidHeight * 2.0 + 0.5, 0.0, 1.0);
liquidFinal *= heightFactor;

float finalAlpha = liquidColor.a * (0.7 + fresnel * 0.3) * heightFactor;

return vec4(liquidFinal, finalAlpha);
}
}%

🔧 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
import { Component, Material, Vec3, _decorator } from 'cc';

const { ccclass, property, menu } = _decorator;

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

@property({ range: [0.0, 3.0], displayName: '动画强度' })
public animationIntensity: number = 1.0;

@property({ range: [0.0, 10.0], displayName: '动画速度' })
public animationSpeed: number = 1.0;

@property({ type: Vec3, displayName: '动画方向' })
public animationDirection: Vec3 = new Vec3(1, 0, 0);

@property({ displayName: '动画类型' })
public animationType: AnimationType = AnimationType.Wave;

// 波浪动画参数
@property({ range: [0.0, 3.0], displayName: '波浪幅度' })
public waveAmplitude: number = 0.5;

@property({ range: [0.1, 10.0], displayName: '波浪频率' })
public waveFrequency: number = 2.0;

// 爆炸动画参数
@property({ type: Vec3, displayName: '爆炸中心' })
public explosionCenter: Vec3 = new Vec3(0, 0, 0);

@property({ range: [0.1, 10.0], displayName: '爆炸半径' })
public explosionRadius: number = 2.0;

@property({ range: [0.0, 10.0], displayName: '爆炸力度' })
public explosionForce: number = 3.0;

@property({ range: [0.0, 5.0], displayName: '爆炸时间' })
public explosionTime: number = 0.0;

// 生长动画参数
@property({ range: [0.0, 1.0], displayName: '生长进度' })
public growthProgress: number = 0.5;

@property({ type: Vec3, displayName: '生长原点' })
public growthOrigin: Vec3 = new Vec3(0, -1, 0);

@property({ range: [0.1, 5.0], displayName: '生长高度' })
public growthHeight: number = 2.0;

// 液体模拟参数
@property({ range: [0.1, 5.0], displayName: '液体密度' })
public liquidDensity: number = 1.0;

@property({ range: [0.0, 2.0], displayName: '粘度' })
public viscosity: number = 0.5;

@property({ range: [0.0, 1.0], displayName: '湍流强度' })
public turbulence: number = 0.3;

private _material: Material | null = null;
private _isAnimating: boolean = false;
private _animationTimer: number = 0;

// 动画类型枚举
public enum AnimationType {
Wave = 'wave-displacement',
Explosion = 'explosion-displacement',
Growth = 'growth-animation',
Liquid = 'liquid-simulation'
}

onLoad() {
const renderer = this.getComponent('cc.MeshRenderer');
if (renderer && renderer.material) {
this._material = renderer.material;
this.updateMaterial();
}
}

update(deltaTime: number) {
if (this._isAnimating) {
this._animationTimer += deltaTime;
this.updateMaterial();
}
}

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

// 通用动画参数
this._material.setProperty('animationIntensity', this.animationIntensity);
this._material.setProperty('animationSpeed', this.animationSpeed);
this._material.setProperty('animationDirection', this.animationDirection);

// 波浪动画参数
this._material.setProperty('waveAmplitude', this.waveAmplitude);
this._material.setProperty('waveFrequency', this.waveFrequency);
this._material.setProperty('waveSpeed', this.animationSpeed);
this._material.setProperty('waveDirection', this.animationDirection);

// 爆炸动画参数
this._material.setProperty('explosionCenter', this.explosionCenter);
this._material.setProperty('explosionRadius', this.explosionRadius);
this._material.setProperty('explosionForce', this.explosionForce);
this._material.setProperty('explosionTime', this.explosionTime);

// 生长动画参数
this._material.setProperty('growthProgress', this.growthProgress);
this._material.setProperty('growthOrigin', this.growthOrigin);
this._material.setProperty('growthHeight', this.growthHeight);

// 液体模拟参数
this._material.setProperty('liquidDensity', this.liquidDensity);
this._material.setProperty('viscosity', this.viscosity);
this._material.setProperty('turbulence', this.turbulence);
}

// 公共接口
public setAnimationType(type: AnimationType) {
this.animationType = type;
console.log(`切换到顶点动画类�? ${type}`);
this.updateMaterial();
}

public startAnimation() {
this._isAnimating = true;
this._animationTimer = 0;
}

public stopAnimation() {
this._isAnimating = false;
}

public resetAnimation() {
this._animationTimer = 0;
this.explosionTime = 0;
this.growthProgress = 0;
this.updateMaterial();
}

// 波浪动画控制
public startWaveAnimation() {
this.setAnimationType(AnimationType.Wave);
this.startAnimation();
}

public setWaveProperties(amplitude: number, frequency: number, speed: number) {
this.waveAmplitude = amplitude;
this.waveFrequency = frequency;
this.animationSpeed = speed;
this.updateMaterial();
}

// 爆炸动画控制
public triggerExplosion(center: Vec3, force: number = 3.0, radius: number = 2.0) {
this.explosionCenter.set(center);
this.explosionForce = force;
this.explosionRadius = radius;
this.explosionTime = 0;

this.setAnimationType(AnimationType.Explosion);

// 爆炸动画序列
let elapsed = 0;
const explode = (dt: number) => {
elapsed += dt;
this.explosionTime = elapsed;
this.updateMaterial();

if (elapsed < 3.0) {
this.scheduleOnce(explode, 0);
} else {
this.explosionTime = 0;
this.updateMaterial();
}
};

this.scheduleOnce(explode, 0);
}

// 生长动画控制
public startGrowthAnimation(duration: number = 3.0) {
this.growthProgress = 0;
this.setAnimationType(AnimationType.Growth);

let elapsed = 0;
const grow = (dt: number) => {
elapsed += dt;
this.growthProgress = Math.min(elapsed / duration, 1.0);
this.updateMaterial();

if (this.growthProgress < 1.0) {
this.scheduleOnce(grow, 0);
}
};

this.scheduleOnce(grow, 0);
}

public setGrowthProperties(origin: Vec3, height: number) {
this.growthOrigin.set(origin);
this.growthHeight = height;
this.updateMaterial();
}

// 液体模拟控制
public startLiquidSimulation() {
this.setAnimationType(AnimationType.Liquid);
this.startAnimation();
}

public setLiquidProperties(density: number, viscosity: number, turbulence: number) {
this.liquidDensity = density;
this.viscosity = viscosity;
this.turbulence = turbulence;
this.updateMaterial();
}

// 预设动画效果
public applyOceanWaves() {
this.setWaveProperties(0.8, 1.5, 1.2);
this.animationDirection.set(1, 0, 0.3);
this.startWaveAnimation();
}

public applyWindEffect() {
this.setWaveProperties(0.3, 3.0, 2.5);
this.animationDirection.set(0.8, 0.2, 0);
this.startWaveAnimation();
}

public applyPlantGrowth() {
this.setGrowthProperties(new Vec3(0, -1, 0), 2.5);
this.startGrowthAnimation(4.0);
}

public applyBuildingCollapse() {
this.triggerExplosion(new Vec3(0, 0, 0), 5.0, 3.0);
}

public applyWaterFlow() {
this.setLiquidProperties(1.2, 0.8, 0.4);
this.startLiquidSimulation();
}

public applyMagicEffect() {
this.setWaveProperties(0.4, 4.0, 3.0);
this.animationDirection.set(0, 1, 0);
this.startWaveAnimation();
}

// 动画序列控制
public playAnimationSequence(animations: AnimationType[], durations: number[]) {
if (animations.length !== durations.length) {
console.error('动画类型和持续时间数组长度不匹配');
return;
}

let currentIndex = 0;

const playNext = () => {
if (currentIndex >= animations.length) {
return;
}

const currentType = animations[currentIndex];
const currentDuration = durations[currentIndex];

this.setAnimationType(currentType);

if (currentType === AnimationType.Growth) {
this.startGrowthAnimation(currentDuration);
} else if (currentType === AnimationType.Explosion) {
this.triggerExplosion(this.explosionCenter, this.explosionForce, this.explosionRadius);
} else {
this.startAnimation();
}

currentIndex++;

if (currentIndex < animations.length) {
this.scheduleOnce(playNext, currentDuration);
}
};

playNext();
}

public resetToDefault() {
this.animationIntensity = 1.0;
this.animationSpeed = 1.0;
this.animationDirection.set(1, 0, 0);
this.waveAmplitude = 0.5;
this.waveFrequency = 2.0;
this.explosionTime = 0;
this.growthProgress = 0.5;
this.liquidDensity = 1.0;
this.viscosity = 0.5;
this.turbulence = 0.3;
this.stopAnimation();
this.updateMaterial();
}
}

// 导出枚举
export { AnimationType } from './VertexAnimationController';

📖 本章总结

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

  • �?顶点动画的基本原理和GPU计算优势
  • �?波浪、爆炸、生长等基础动画实现
  • �?液体模拟和高级形变技�?- �?完整的TypeScript控制系统和动画序�?- �?各种游戏场景中顶点动画的实际应用

🚀 下一步学�?

掌握�?D顶点动画着色器后,建议继续学习�?
👉 �?1章:渲染管线与优化

💡 实践练习

  1. 创建一个海洋波浪的真实模拟系统
  2. 实现建筑物倒塌的爆炸动�?3. 开发植物生长和季节变化特效

系列导航