第10.1章:3D高级边缘光着色器

边缘光(Rim Light)是3D渲染中最经典的视觉增强技术之一,能够突出物体轮廓、营造氛围感。本章将深入探讨高级边缘光技术的实现。

🎯 学习目标

  • 理解菲涅尔反射的物理原理
  • 掌握多种边缘光计算方�?- 学会实现动态和交互式边缘光
  • 理解3D空间中的高级光照效果

💡 边缘光原�?

菲涅尔反射基础

边缘光基于菲涅尔反射原理,当观察角度接近表面切线时反射率增强�?

1
2
// 基础菲涅尔计�?float fresnel = 1.0 - dot(normal, viewDirection);
fresnel = pow(fresnel, fresnelPower);

物理基础

1
视线角度 �?法线夹角 �?菲涅尔�?�?边缘光强�?    �?        �?        �?        �?  camera    normal   fresnel   rim light

🔧 基础边缘光实�?

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
CCEffect %{
techniques:
- name: basic-rim-light
passes:
- vert: rim-vs:vert
frag: rim-fs:frag
properties: &props
mainTexture: { value: white }
rimColor: { value: [0.5, 0.8, 1.0, 1.0], editor: { type: color } }
rimPower: { value: 2.0, range: [0.1, 10.0] }
rimIntensity: { value: 1.0, range: [0.0, 5.0] }
rimOffset: { value: 0.0, range: [-1.0, 1.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

CCProgram rim-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform vec4 rimColor;
uniform float rimPower;
uniform float rimIntensity;
uniform float rimOffset;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

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

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

// 边缘光计�? float fresnel = 1.0 - max(0.0, dot(normal, viewDir));
fresnel = pow(fresnel + rimOffset, rimPower) * rimIntensity;

// 合成最终颜�? vec3 finalColor = albedo.rgb + rimColor.rgb * fresnel;

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
CCEffect %{
techniques:
- name: multi-rim-light
passes:
- vert: multi-rim-vs:vert
frag: multi-rim-fs:frag
properties: &props
mainTexture: { value: white }
rimColor1: { value: [1.0, 0.3, 0.3, 1.0], editor: { type: color } }
rimColor2: { value: [0.3, 0.3, 1.0, 1.0], editor: { type: color } }
rimColor3: { value: [0.3, 1.0, 0.3, 1.0], editor: { type: color } }
rimPower1: { value: 1.5, range: [0.1, 10.0] }
rimPower2: { value: 3.0, range: [0.1, 10.0] }
rimPower3: { value: 5.0, range: [0.1, 10.0] }
rimIntensity1: { value: 0.8, range: [0.0, 3.0] }
rimIntensity2: { value: 0.6, range: [0.0, 3.0] }
rimIntensity3: { value: 0.4, range: [0.0, 3.0] }
layerBlendMode: { value: 0.0, range: [0.0, 2.0] } # 0=add, 1=multiply, 2=overlay
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

CCProgram multi-rim-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform vec4 rimColor1;
uniform vec4 rimColor2;
uniform vec4 rimColor3;
uniform float rimPower1;
uniform float rimPower2;
uniform float rimPower3;
uniform float rimIntensity1;
uniform float rimIntensity2;
uniform float rimIntensity3;
uniform float layerBlendMode;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

vec3 blendColors(vec3 base, vec3 overlay, float mode) {
if (mode < 0.5) {
// Add mode
return base + overlay;
} else if (mode < 1.5) {
// Multiply mode
return base * overlay;
} else {
// Overlay mode
return mix(2.0 * base * overlay, 1.0 - 2.0 * (1.0 - base) * (1.0 - overlay), step(0.5, base));
}
}

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

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

// 菲涅尔基础�? float fresnelBase = 1.0 - max(0.0, dot(normal, viewDir));

// 多层边缘光计�? float rim1 = pow(fresnelBase, rimPower1) * rimIntensity1;
float rim2 = pow(fresnelBase, rimPower2) * rimIntensity2;
float rim3 = pow(fresnelBase, rimPower3) * rimIntensity3;

// 边缘光颜色混�? vec3 rimFinal = rimColor1.rgb * rim1;
rimFinal = blendColors(rimFinal, rimColor2.rgb * rim2, layerBlendMode);
rimFinal = blendColors(rimFinal, rimColor3.rgb * rim3, layerBlendMode);

// 合成最终颜�? vec3 finalColor = albedo.rgb + rimFinal;

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

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
CCEffect %{
techniques:
- name: pulse-rim-light
passes:
- vert: pulse-rim-vs:vert
frag: pulse-rim-fs:frag
properties: &props
mainTexture: { value: white }
rimColor: { value: [0.0, 0.8, 1.0, 1.0], editor: { type: color } }
rimPower: { value: 2.0, range: [0.1, 10.0] }
rimIntensity: { value: 1.5, range: [0.0, 5.0] }
pulseSpeed: { value: 2.0, range: [0.0, 10.0] }
pulseAmplitude: { value: 0.7, range: [0.0, 1.0] }
waveFrequency: { value: 1.0, range: [0.1, 5.0] }
waveOffset: { value: 0.0, range: [0.0, 6.28] }
noiseScale: { value: 1.0, range: [0.1, 10.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

CCProgram pulse-rim-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform vec4 rimColor;
uniform float rimPower;
uniform float rimIntensity;
uniform float pulseSpeed;
uniform float pulseAmplitude;
uniform float waveFrequency;
uniform float waveOffset;
uniform float noiseScale;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

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

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

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

// 基础菲涅�? float fresnel = 1.0 - max(0.0, dot(normal, viewDir));

// 脉冲动画
float pulse = sin(cc_time.x * pulseSpeed) * pulseAmplitude + (1.0 - pulseAmplitude);

// 空间变化波形
float spatialWave = sin(v_worldPos.y * waveFrequency + cc_time.x * pulseSpeed + waveOffset);
spatialWave = spatialWave * 0.5 + 0.5;

// 噪声扰动
float noiseValue = noise(v_worldPos * noiseScale + cc_time.x * 0.5);
noiseValue = noiseValue * 0.3 + 0.7; // 重新映射�?.7-1.0范围

// 合成动态强�? float dynamicIntensity = rimIntensity * pulse * spatialWave * noiseValue;

// 边缘光计�? float rim = pow(fresnel, rimPower) * dynamicIntensity;

// 颜色变化
vec3 dynamicRimColor = rimColor.rgb;
dynamicRimColor.r += sin(cc_time.x * pulseSpeed * 1.3) * 0.2;
dynamicRimColor.g += sin(cc_time.x * pulseSpeed * 1.7) * 0.2;
dynamicRimColor.b += sin(cc_time.x * pulseSpeed * 2.1) * 0.2;

// 合成最终颜�? vec3 finalColor = albedo.rgb + dynamicRimColor * rim;

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
CCEffect %{
techniques:
- name: energy-shield-rim
passes:
- vert: shield-vs:vert
frag: shield-fs:frag
properties: &props
mainTexture: { value: white }
noiseTexture: { value: white }
shieldColor: { value: [0.2, 0.8, 1.0, 1.0], editor: { type: color } }
energyFlow: { value: [1.0, 1.0], editor: { type: vec2 } }
energySpeed: { value: 2.0, range: [0.0, 10.0] }
distortionStrength: { value: 0.1, range: [0.0, 0.5] }
hexPatternScale: { value: 20.0, range: [5.0, 50.0] }
hexVisible: { value: 1.0, range: [0.0, 1.0] }
rimPower: { value: 1.5, range: [0.1, 5.0] }
rimIntensity: { value: 2.0, range: [0.0, 5.0] }
transparency: { value: 0.7, range: [0.0, 1.0] }
}%

CCProgram shield-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform sampler2D noiseTexture;
uniform vec4 shieldColor;
uniform vec2 energyFlow;
uniform float energySpeed;
uniform float distortionStrength;
uniform float hexPatternScale;
uniform float hexVisible;
uniform float rimPower;
uniform float rimIntensity;
uniform float transparency;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

// 六边形网格模�? float hexPattern(vec2 uv) {
vec2 grid = abs(fract(uv * hexPatternScale) - 0.5);
return smoothstep(0.4, 0.5, max(grid.x, grid.y));
}

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

// 能量流动UV
vec2 flowUV = v_uv + energyFlow * cc_time.x * energySpeed;

// 噪声扰动
vec4 noise = texture(noiseTexture, flowUV);
vec2 distortedUV = v_uv + (noise.rg - 0.5) * distortionStrength;

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

// 边缘光计�? float fresnel = 1.0 - max(0.0, dot(normal, viewDir));
float rim = pow(fresnel, rimPower) * rimIntensity;

// 六边形网�? float hexGrid = hexPattern(distortedUV);

// 能量脉冲
float pulse = sin(cc_time.x * energySpeed * 2.0) * 0.3 + 0.7;

// 合成护盾效果
vec3 energyColor = shieldColor.rgb * (rim + noise.b * 0.5) * pulse;
energyColor += hexGrid * hexVisible * shieldColor.rgb * 0.3;

// 最终颜�? vec3 finalColor = mix(albedo.rgb, energyColor, transparency);
float finalAlpha = max(albedo.a, rim * transparency);

return vec4(finalColor, finalAlpha);
}
}%

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
CCEffect %{
techniques:
- name: rainbow-rim-light
passes:
- vert: rainbow-rim-vs:vert
frag: rainbow-rim-fs:frag
properties: &props
mainTexture: { value: white }
rainbowSpeed: { value: 1.0, range: [0.0, 5.0] }
rainbowScale: { value: 3.0, range: [0.5, 10.0] }
saturation: { value: 1.0, range: [0.0, 2.0] }
brightness: { value: 1.0, range: [0.0, 3.0] }
rimPower: { value: 2.0, range: [0.1, 10.0] }
rimIntensity: { value: 1.5, range: [0.0, 5.0] }
spatialOffset: { value: 1.0, range: [0.0, 5.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

CCProgram rainbow-rim-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform float rainbowSpeed;
uniform float rainbowScale;
uniform float saturation;
uniform float brightness;
uniform float rimPower;
uniform float rimIntensity;
uniform float spatialOffset;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

// 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() {
vec3 normal = normalize(v_worldNormal);
vec3 viewDir = normalize(cc_cameraPos.xyz - v_worldPos);

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

// 边缘光计�? float fresnel = 1.0 - max(0.0, dot(normal, viewDir));
float rim = pow(fresnel, rimPower) * rimIntensity;

// 彩虹色调计算
float hue = fract((v_worldPos.x + v_worldPos.y + v_worldPos.z) * rainbowScale * 0.1 + cc_time.x * rainbowSpeed);
vec3 rainbowColor = hsv2rgb(vec3(hue, saturation, brightness));

// 空间变化
float spatialVar = sin(v_worldPos.y * spatialOffset) * 0.5 + 0.5;
rainbowColor *= spatialVar * 0.5 + 0.5;

// 合成最终颜�? vec3 finalColor = albedo.rgb + rainbowColor * rim;

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

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
CCEffect %{
techniques:
- name: electric-rim-light
passes:
- vert: electric-vs:vert
frag: electric-fs:frag
properties: &props
mainTexture: { value: white }
noiseTexture: { value: white }
electricColor: { value: [0.3, 0.7, 1.0, 1.0], editor: { type: color } }
boltCount: { value: 8.0, range: [3.0, 20.0] }
boltSpeed: { value: 3.0, range: [0.0, 10.0] }
boltIntensity: { value: 2.0, range: [0.0, 5.0] }
boltThickness: { value: 0.05, range: [0.01, 0.2] }
arcHeight: { value: 0.3, range: [0.1, 1.0] }
rimPower: { value: 1.8, range: [0.1, 5.0] }
rimIntensity: { value: 1.0, range: [0.0, 3.0] }
flickerSpeed: { value: 15.0, range: [5.0, 30.0] }
baseColor: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
}%

CCProgram electric-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;

vec4 vert() {
vec4 worldPos = cc_matWorld * vec4(a_position, 1);
v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

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

uniform sampler2D mainTexture;
uniform sampler2D noiseTexture;
uniform vec4 electricColor;
uniform float boltCount;
uniform float boltSpeed;
uniform float boltIntensity;
uniform float boltThickness;
uniform float arcHeight;
uniform float rimPower;
uniform float rimIntensity;
uniform float flickerSpeed;
uniform vec4 baseColor;

in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec2 v_uv;

// 生成电弧模式
float electricArc(vec2 uv, float time) {
float electric = 0.0;

for (float i = 0.0; i < boltCount; i++) {
float angle = (i / boltCount) * 6.28318 + time * boltSpeed;
vec2 boltDir = vec2(cos(angle), sin(angle));

// 电弧路径
float dist = dot(uv - 0.5, boltDir);
float perpDist = length(uv - 0.5 - boltDir * dist);

// 电弧形状
float arc = arcHeight * sin(dist * 10.0 + time * boltSpeed * 2.0);
float boltMask = 1.0 - smoothstep(0.0, boltThickness, abs(perpDist - arc));

// 添加噪声
vec2 noiseUV = uv + boltDir * time * 0.1;
float noise = texture(noiseTexture, noiseUV * 5.0).r;
boltMask *= noise;

electric += boltMask;
}

return electric;
}

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

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

// 边缘光计�? float fresnel = 1.0 - max(0.0, dot(normal, viewDir));
float rim = pow(fresnel, rimPower) * rimIntensity;

// 闪烁效果
float flicker = sin(cc_time.x * flickerSpeed) * 0.3 + 0.7;
flicker *= sin(cc_time.x * flickerSpeed * 1.7) * 0.2 + 0.8;

// 电弧效果
float electric = electricArc(v_uv, cc_time.x) * boltIntensity * flicker;

// 合成电流边缘�? vec3 electricRim = electricColor.rgb * (rim + electric * 0.5);

// 最终颜�? vec3 finalColor = albedo.rgb + electricRim;

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

🔧 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
import { Component, Material, Color, Vec3, _decorator } from 'cc';

const { ccclass, property, menu } = _decorator;

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

@property({ type: Color, displayName: '边缘光颜�? })
public rimColor: Color = new Color(128, 200, 255, 255);

@property({ range: [0.1, 10.0], displayName: '边缘光强�? })
public rimPower: number = 2.0;

@property({ range: [0.0, 5.0], displayName: '边缘光亮�? })
public rimIntensity: number = 1.0;

@property({ range: [-1.0, 1.0], displayName: '边缘光偏�? })
public rimOffset: number = 0.0;

@property({ displayName: '启用动态效�? })
public enableDynamicEffect: boolean = false;

@property({ range: [0.0, 10.0], displayName: '脉冲速度' })
public pulseSpeed: number = 2.0;

@property({ range: [0.0, 1.0], displayName: '脉冲幅度' })
public pulseAmplitude: number = 0.7;

@property({ displayName: '启用多层效果' })
public enableMultiLayer: boolean = false;

@property({ type: Color, displayName: '第二层颜�? })
public rimColor2: Color = new Color(255, 128, 128, 255);

@property({ type: Color, displayName: '第三层颜�? })
public rimColor3: Color = new Color(128, 255, 128, 255);

@property({ displayName: '特效类型' })
public effectType: EffectType = EffectType.Basic;

private _material: Material | null = null;
private _isAnimating: boolean = false;
private _originalRimColor: Color = new Color();

// 特效类型枚举
public enum EffectType {
Basic = 'basic-rim-light',
MultiLayer = 'multi-rim-light',
Pulse = 'pulse-rim-light',
EnergyShield = 'energy-shield-rim',
Rainbow = 'rainbow-rim-light',
Electric = 'electric-rim-light'
}

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

update(deltaTime: number) {
if (this._isAnimating || this.enableDynamicEffect) {
this.updateMaterial();
}
}

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

// 基础边缘光参�? this._material.setProperty('rimColor', this.rimColor);
this._material.setProperty('rimPower', this.rimPower);
this._material.setProperty('rimIntensity', this.rimIntensity);
this._material.setProperty('rimOffset', this.rimOffset);

// 动态效果参�? if (this.enableDynamicEffect) {
this._material.setProperty('pulseSpeed', this.pulseSpeed);
this._material.setProperty('pulseAmplitude', this.pulseAmplitude);
}

// 多层效果参数
if (this.enableMultiLayer) {
this._material.setProperty('rimColor2', this.rimColor2);
this._material.setProperty('rimColor3', this.rimColor3);
}
}

// 公共接口
public setEffectType(type: EffectType) {
this.effectType = type;
// 这里可以切换不同的材质或technique
console.log(`切换到边缘光类型: ${type}`);
this.updateMaterial();
}

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

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

public setRimColor(color: Color) {
this.rimColor = color;
this.updateMaterial();
}

public setRimIntensity(intensity: number) {
this.rimIntensity = intensity;
this.updateMaterial();
}

// 预设效果
public applyMagicItemEffect() {
this.rimColor = new Color(100, 150, 255, 255);
this.rimPower = 1.5;
this.rimIntensity = 2.0;
this.enableDynamicEffect = true;
this.pulseSpeed = 1.5;
this.pulseAmplitude = 0.5;
this.updateMaterial();
}

public applyLegendaryItemEffect() {
this.rimColor = new Color(255, 215, 0, 255); // 金色
this.rimPower = 2.5;
this.rimIntensity = 2.5;
this.enableMultiLayer = true;
this.rimColor2 = new Color(255, 165, 0, 255); // 橙色
this.rimColor3 = new Color(255, 69, 0, 255); // 红橙�? this.enableDynamicEffect = true;
this.updateMaterial();
}

public applyEnemyEffect() {
this.rimColor = new Color(255, 50, 50, 255); // 红色
this.rimPower = 3.0;
this.rimIntensity = 1.8;
this.enableDynamicEffect = true;
this.pulseSpeed = 3.0;
this.pulseAmplitude = 0.8;
this.updateMaterial();
}

public applyFriendlyEffect() {
this.rimColor = new Color(50, 255, 50, 255); // 绿色
this.rimPower = 2.0;
this.rimIntensity = 1.2;
this.enableDynamicEffect = false;
this.updateMaterial();
}

public applyShieldEffect() {
this.setEffectType(EffectType.EnergyShield);
this.rimColor = new Color(50, 200, 255, 255);
this.rimPower = 1.5;
this.rimIntensity = 2.0;
this.updateMaterial();
}

public applyElectricEffect() {
this.setEffectType(EffectType.Electric);
this.rimColor = new Color(100, 200, 255, 255);
this.rimPower = 1.8;
this.rimIntensity = 2.5;
this.updateMaterial();
}

// 动画效果
public flashRim(duration: number = 1.0) {
const originalIntensity = this.rimIntensity;
const flashIntensity = originalIntensity * 3.0;

let elapsed = 0;
const flash = (dt: number) => {
elapsed += dt;
const t = elapsed / duration;

if (t < 0.3) {
// 快速增强阶�? this.rimIntensity = originalIntensity + (flashIntensity - originalIntensity) * (t / 0.3);
} else {
// 缓慢衰减阶段
this.rimIntensity = flashIntensity - (flashIntensity - originalIntensity) * ((t - 0.3) / 0.7);
}

this.updateMaterial();

if (t < 1.0) {
this.scheduleOnce(flash, 0);
} else {
this.rimIntensity = originalIntensity;
this.updateMaterial();
}
};

this.scheduleOnce(flash, 0);
}

public colorTransition(targetColor: Color, duration: number = 1.0) {
const startColor = this.rimColor.clone();

let elapsed = 0;
const transition = (dt: number) => {
elapsed += dt;
const t = Math.min(elapsed / duration, 1.0);

// 颜色插�? this.rimColor.r = Math.lerp(startColor.r, targetColor.r, t);
this.rimColor.g = Math.lerp(startColor.g, targetColor.g, t);
this.rimColor.b = Math.lerp(startColor.b, targetColor.b, t);
this.rimColor.a = Math.lerp(startColor.a, targetColor.a, t);

this.updateMaterial();

if (t < 1.0) {
this.scheduleOnce(transition, 0);
}
};

this.scheduleOnce(transition, 0);
}

public resetToDefault() {
this.rimColor.set(this._originalRimColor);
this.rimPower = 2.0;
this.rimIntensity = 1.0;
this.rimOffset = 0.0;
this.enableDynamicEffect = false;
this.enableMultiLayer = false;
this.updateMaterial();
}
}

// 导出枚举以便其他脚本使用
export { EffectType } from './AdvancedRimLightController';

📖 本章总结

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

  • �?菲涅尔反射的物理原理和数学实�?- �?标准边缘光、多层边缘光和动态脉冲边缘光
  • �?高级特效:能量护盾、彩虹、电流边缘光
  • �?完整的TypeScript控制系统和预设效�?- �?3D空间中的边缘光优化技�?

🚀 下一步学�?

掌握�?D高级边缘光着色器后,建议继续学习�?
👉 �?0.2章:3D全息投影着色器

💡 实践练习

  1. 创建一个角色选中时的边缘光系�?2. 实现不同品质道具的边缘光效果
  2. 开发科幻风格的能量护盾特效

系列导航