|
1015 | 1015 |
|
1016 | 1016 | /** |
1017 | 1017 | * 创建太阳及其光晕效果 |
1018 | | - * 太阳由主体和三层光晕组成,营造发光效果,光晕会在动画中产生呼吸效果 |
| 1018 | + * 太阳使用自定义shader实现表面火焰闪动效果,配合多层光晕营造发光效果 |
1019 | 1019 | */ |
1020 | 1020 | createSun() { |
1021 | 1021 | const sunGeometry = new THREE.SphereGeometry( |
1022 | 1022 | 3, |
1023 | | - 32, |
1024 | | - 32 |
| 1023 | + 64, |
| 1024 | + 64 |
1025 | 1025 | ) |
1026 | | - const sunMaterial = new THREE.MeshBasicMaterial({ |
1027 | | - color: 0xffd700, |
1028 | | - emissive: 0xffd700, |
1029 | | - emissiveIntensity: 1, |
| 1026 | + |
| 1027 | + // 自定义shader实现火焰闪动效果 |
| 1028 | + const sunVertexShader = ` |
| 1029 | + varying vec3 vPosition; |
| 1030 | + varying vec3 vNormal; |
| 1031 | + varying vec2 vUv; |
| 1032 | + |
| 1033 | + void main() { |
| 1034 | + vPosition = position; |
| 1035 | + vNormal = normalize(normalMatrix * normal); |
| 1036 | + vUv = uv; |
| 1037 | + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); |
| 1038 | + } |
| 1039 | + ` |
| 1040 | + |
| 1041 | + const sunFragmentShader = ` |
| 1042 | + // based on https://www.shadertoy.com/view/lsf3RH by trisomie21 |
| 1043 | + |
| 1044 | + uniform float uTime; |
| 1045 | + uniform vec3 uSunColor; |
| 1046 | + uniform vec3 uFlameColor; |
| 1047 | + |
| 1048 | + varying vec3 vPosition; |
| 1049 | + varying vec3 vNormal; |
| 1050 | + varying vec2 vUv; |
| 1051 | + |
| 1052 | + // 3D噪声函数 by trisomie21 |
| 1053 | + float snoise(vec3 uv, float res) { |
| 1054 | + const vec3 s = vec3(1e0, 1e2, 1e4); |
| 1055 | + |
| 1056 | + uv *= res; |
| 1057 | + |
| 1058 | + vec3 uv0 = floor(mod(uv, res)) * s; |
| 1059 | + vec3 uv1 = floor(mod(uv + vec3(1.0), res)) * s; |
| 1060 | + |
| 1061 | + vec3 f = fract(uv); |
| 1062 | + f = f * f * (3.0 - 2.0 * f); |
| 1063 | + |
| 1064 | + vec4 v = vec4(uv0.x + uv0.y + uv0.z, uv1.x + uv0.y + uv0.z, |
| 1065 | + uv0.x + uv1.y + uv0.z, uv1.x + uv1.y + uv0.z); |
| 1066 | + |
| 1067 | + vec4 r = fract(sin(v * 1e-3) * 1e5); |
| 1068 | + float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); |
| 1069 | + |
| 1070 | + r = fract(sin((v + uv1.z - uv0.z) * 1e-3) * 1e5); |
| 1071 | + float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); |
| 1072 | + |
| 1073 | + return mix(r0, r1, f.z) * 2.0 - 1.0; |
| 1074 | + } |
| 1075 | + |
| 1076 | + void main() { |
| 1077 | + // 模拟音频频率(使用时间变化代替) |
| 1078 | + float freqs[4]; |
| 1079 | + freqs[0] = 0.3 + sin(uTime * 2.0) * 0.2; |
| 1080 | + freqs[1] = 0.4 + sin(uTime * 3.0) * 0.3; |
| 1081 | + freqs[2] = 0.5 + sin(uTime * 4.0) * 0.25; |
| 1082 | + freqs[3] = 0.3 + sin(uTime * 5.0) * 0.2; |
| 1083 | + |
| 1084 | + float brightness = freqs[1] * 0.25 + freqs[2] * 0.25; |
| 1085 | + float radius = 0.24 + brightness * 0.2; |
| 1086 | + float invRadius = 1.0 / radius; |
| 1087 | + |
| 1088 | + vec3 orange = vec3(0.8, 0.65, 0.3); |
| 1089 | + vec3 orangeRed = vec3(0.8, 0.35, 0.1); |
| 1090 | + float time = uTime * 0.1; |
| 1091 | + |
| 1092 | + // 使用UV坐标,适配到球体 |
| 1093 | + vec2 uv = vUv; |
| 1094 | + vec2 p = -0.5 + uv; |
| 1095 | + float aspect = 1.0; |
| 1096 | + p.x *= aspect; |
| 1097 | + |
| 1098 | + float fade = pow(length(2.0 * p), 0.5); |
| 1099 | + float fVal1 = 1.0 - fade; |
| 1100 | + float fVal2 = 1.0 - fade; |
| 1101 | + |
| 1102 | + float angle = atan(p.x, p.y) / 6.2832; |
| 1103 | + float dist = length(p); |
| 1104 | + vec3 coord = vec3(angle, dist, time * 0.1); |
| 1105 | + |
| 1106 | + float newTime1 = abs(snoise(coord + vec3(0.0, -time * (0.35 + brightness * 0.001), time * 0.015), 15.0)); |
| 1107 | + float newTime2 = abs(snoise(coord + vec3(0.0, -time * (0.15 + brightness * 0.001), time * 0.015), 45.0)); |
| 1108 | + |
| 1109 | + for (int i = 1; i <= 7; i++) { |
| 1110 | + float power = pow(2.0, float(i + 1)); |
| 1111 | + fVal1 += (0.5 / power) * snoise(coord + vec3(0.0, -time, time * 0.2), (power * (10.0) * (newTime1 + 1.0))); |
| 1112 | + fVal2 += (0.5 / power) * snoise(coord + vec3(0.0, -time, time * 0.2), (power * (25.0) * (newTime2 + 1.0))); |
| 1113 | + } |
| 1114 | + |
| 1115 | + float corona = pow(fVal1 * max(1.1 - fade, 0.0), 2.0) * 50.0; |
| 1116 | + corona += pow(fVal2 * max(1.1 - fade, 0.0), 2.0) * 50.0; |
| 1117 | + corona *= 1.2 - newTime1; |
| 1118 | + |
| 1119 | + vec3 starSphere = vec3(0.0); |
| 1120 | + |
| 1121 | + vec2 sp = -1.0 + 2.0 * uv; |
| 1122 | + sp.x *= aspect; |
| 1123 | + sp *= (2.0 - brightness); |
| 1124 | + float r = dot(sp, sp); |
| 1125 | + float f = (1.0 - sqrt(abs(1.0 - r))) / (r) + brightness * 0.5; |
| 1126 | + |
| 1127 | + if (dist < radius) { |
| 1128 | + corona *= pow(dist * invRadius, 24.0); |
| 1129 | + vec2 newUv; |
| 1130 | + newUv.x = sp.x * f; |
| 1131 | + newUv.y = sp.y * f; |
| 1132 | + newUv += vec2(time, 0.0); |
| 1133 | + |
| 1134 | + // 使用噪声代替纹理采样 |
| 1135 | + vec3 texSample = vec3( |
| 1136 | + snoise(vec3(newUv, time), 10.0) * 0.5 + 0.5, |
| 1137 | + snoise(vec3(newUv, time + 1.0), 10.0) * 0.5 + 0.5, |
| 1138 | + snoise(vec3(newUv, time + 2.0), 10.0) * 0.5 + 0.5 |
| 1139 | + ); |
| 1140 | + |
| 1141 | + float uOff = (texSample.g * brightness * 4.5 + time); |
| 1142 | + vec2 starUV = newUv + vec2(uOff, 0.0); |
| 1143 | + starSphere = vec3( |
| 1144 | + snoise(vec3(starUV, time), 8.0) * 0.5 + 0.5, |
| 1145 | + snoise(vec3(starUV, time + 1.0), 8.0) * 0.5 + 0.5, |
| 1146 | + snoise(vec3(starUV, time + 2.0), 8.0) * 0.5 + 0.5 |
| 1147 | + ) * 0.3; |
| 1148 | + } |
| 1149 | + |
| 1150 | + float starGlow = min(max(1.0 - dist * (1.0 - brightness), 0.0), 1.0); |
| 1151 | + |
| 1152 | + vec3 finalColor = vec3(f * (0.75 + brightness * 0.3) * orange) + starSphere + corona * orange + starGlow * orangeRed; |
| 1153 | + |
| 1154 | + gl_FragColor = vec4(finalColor, 1.0); |
| 1155 | + } |
| 1156 | + ` |
| 1157 | + |
| 1158 | + const sunMaterial = new THREE.ShaderMaterial({ |
| 1159 | + uniforms: { |
| 1160 | + uTime: { value: 0.0 }, |
| 1161 | + uSunColor: { |
| 1162 | + value: new THREE.Color(0xffd700), |
| 1163 | + }, |
| 1164 | + uFlameColor: { |
| 1165 | + value: new THREE.Color(0xff4500), |
| 1166 | + }, |
| 1167 | + }, |
| 1168 | + vertexShader: sunVertexShader, |
| 1169 | + fragmentShader: sunFragmentShader, |
1030 | 1170 | }) |
| 1171 | + |
1031 | 1172 | const sun = new THREE.Mesh(sunGeometry, sunMaterial) |
1032 | 1173 | this.scene.add(sun) |
| 1174 | + this.sun = sun // 保存引用用于更新uniform |
1033 | 1175 |
|
1034 | 1176 | const glowGeometry1 = new THREE.SphereGeometry( |
1035 | | - 3.5, |
| 1177 | + 3.2, |
1036 | 1178 | 32, |
1037 | 1179 | 32 |
1038 | 1180 | ) |
|
1048 | 1190 | this.scene.add(sunGlow1) |
1049 | 1191 |
|
1050 | 1192 | const glowGeometry2 = new THREE.SphereGeometry( |
1051 | | - 4.2, |
| 1193 | + 3.4, |
1052 | 1194 | 32, |
1053 | 1195 | 32 |
1054 | 1196 | ) |
|
1064 | 1206 | this.scene.add(sunGlow2) |
1065 | 1207 |
|
1066 | 1208 | const glowGeometry3 = new THREE.SphereGeometry( |
1067 | | - 5.0, |
| 1209 | + 3.8, |
1068 | 1210 | 32, |
1069 | 1211 | 32 |
1070 | 1212 | ) |
|
1550 | 1692 | planet.mesh.rotation.y += 0.01 * this.speed |
1551 | 1693 | }) |
1552 | 1694 |
|
| 1695 | + // 更新太阳光晕动画 |
1553 | 1696 | if (this.sunGlows) { |
1554 | 1697 | this.sunGlows.forEach((glow, index) => { |
1555 | 1698 | const time = Date.now() * 0.001 |
|
0 commit comments