This commit is contained in:
2026-03-26 13:31:09 +08:00
parent c1f2d0ed2b
commit 9b4dfb68b0
3 changed files with 140 additions and 0 deletions

View File

@@ -348,6 +348,26 @@
</div>
</div>
<div class="control-section">
<div class="control-section-title">雪效</div>
<div class="control-toggle">
<label for="snow-enabled">启用降雪</label>
<input type="checkbox" id="snow-enabled">
</div>
<div class="control-grid">
<div class="control-group">
<label>雪量</label>
<input type="range" id="snow-intensity" min="0" max="1.5" value="0.65" step="0.01">
<div class="control-value" id="snow-intensity-value">0.65</div>
</div>
<div class="control-group">
<label>速度</label>
<input type="range" id="snow-speed" min="0.2" max="2.2" value="0.85" step="0.01">
<div class="control-value" id="snow-speed-value">0.85</div>
</div>
</div>
</div>
<div class="control-section">
<div class="control-section-title">雷闪</div>
<div class="control-toggle">

View File

@@ -34,6 +34,7 @@ export class OceanScene {
this.composer = null;
this.bloomPass = null;
this.rainPass = null;
this.snowPass = null;
this.cloudGroup = null;
this.cloudMaterials = [];
this.cloudLayers = [];
@@ -72,6 +73,9 @@ export class OceanScene {
rainSpeed: 1.0,
rainAudioEnabled: true,
rainAudioVolume: 0.35,
snowEnabled: false,
snowIntensity: 0.65,
snowSpeed: 0.85,
lightningEnabled: true,
lightningIntensity: 0.75,
mieCoefficient: 0.005,
@@ -192,6 +196,13 @@ export class OceanScene {
this.rainPass.material.uniforms.dropSize.value = this.params.rainDropSize;
this.rainPass.material.uniforms.rainSpeed.value = this.params.rainSpeed;
this.composer.addPass(this.rainPass);
this.snowPass = new ShaderPass(this.createSnowShader());
this.snowPass.enabled = this.params.snowEnabled;
this.snowPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
this.snowPass.material.uniforms.intensity.value = this.params.snowIntensity;
this.snowPass.material.uniforms.snowSpeed.value = this.params.snowSpeed;
this.composer.addPass(this.snowPass);
}
initAudio() {
@@ -441,6 +452,85 @@ export class OceanScene {
`
};
}
createSnowShader() {
return {
uniforms: {
tDiffuse: { value: null },
time: { value: 0 },
intensity: { value: this.params.snowIntensity },
snowSpeed: { value: this.params.snowSpeed },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform float intensity;
uniform float snowSpeed;
uniform vec2 resolution;
varying vec2 vUv;
float rnd(float x) {
return fract(sin(dot(vec2(x + 47.49, 38.2467 / (x + 2.3)), vec2(12.9898, 78.233))) * 43758.5453);
}
float drawCircle(vec2 uv, vec2 center, float radius) {
return 1.0 - smoothstep(0.0, radius, length(uv - center));
}
float snowField(vec2 uv, float t, float amount, float aspect) {
float snow = 0.0;
float blizzardFactor = mix(0.08, 0.3, amount);
int flakeCount = 220;
for (int i = 0; i < 220; i++) {
if (i >= flakeCount) break;
float j = float(i);
float baseRnd = rnd(cos(j));
float speed = (0.3 + baseRnd * (0.7 + 0.5 * cos(j / 55.0))) * mix(0.7, 1.65, amount);
float radius = (0.001 + speed * 0.012) * mix(0.7, 1.18, amount);
vec2 center = vec2(
((0.25 - uv.y) * blizzardFactor + rnd(j) + 0.08 * cos(t * 0.7 + sin(j))) * aspect,
mod(sin(j) - speed * (t * 1.5 * (0.1 + blizzardFactor)), 1.35) - 0.25
);
float flake = drawCircle(uv, center, radius);
snow += flake * (0.035 + speed * 0.04);
}
return snow;
}
void main() {
vec2 uv = vUv;
float aspect = resolution.x / max(resolution.y, 1.0);
vec2 snowUv = vec2(vUv.x * aspect, vUv.y);
float snowAmount = clamp(intensity / 1.5, 0.0, 1.0);
float t = time * snowSpeed;
float snow = snowField(snowUv, t, snowAmount, aspect);
float snowMask = clamp(snow * mix(0.45, 1.15, snowAmount), 0.0, 1.0);
float atmosphere = (1.0 - vUv.y) * 0.12 * snowAmount;
vec3 base = texture2D(tDiffuse, vUv).rgb;
vec3 snowTint = vec3(0.92, 0.95, 1.0);
base = mix(base, base * 0.96 + snowTint * 0.04, snowAmount * 0.1);
base += snowTint * snowMask;
base += vec3(0.16, 0.28, 0.4) * atmosphere;
gl_FragColor = vec4(base, 1.0);
}
`
};
}
async initSky() {
this.sky = new Sky();
@@ -973,6 +1063,9 @@ export class OceanScene {
if (this.rainPass) {
this.rainPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
}
if (this.snowPass) {
this.snowPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
}
}
setSunElevation(value) {
@@ -1111,6 +1204,27 @@ export class OceanScene {
this.updateRainAudioState();
}
setSnowEnabled(value) {
this.params.snowEnabled = value;
if (this.snowPass) {
this.snowPass.enabled = value;
}
}
setSnowIntensity(value) {
this.params.snowIntensity = value;
if (this.snowPass) {
this.snowPass.material.uniforms.intensity.value = value;
}
}
setSnowSpeed(value) {
this.params.snowSpeed = value;
if (this.snowPass) {
this.snowPass.material.uniforms.snowSpeed.value = value;
}
}
updateRainAudioState() {
if (this.rainAudioPool.length === 0) return;
@@ -1465,6 +1579,9 @@ export class OceanScene {
if (this.rainPass) {
this.rainPass.material.uniforms.time.value = time;
}
if (this.snowPass) {
this.snowPass.material.uniforms.time.value = time;
}
this.controls.update();
if (this.composer) {

View File

@@ -69,9 +69,12 @@ function setupControls(oceanScene) {
bindSlider('rain-drop-size', (value) => value.toFixed(2), (value) => oceanScene.setRainDropSize(value));
bindSlider('rain-speed', (value) => value.toFixed(2), (value) => oceanScene.setRainSpeed(value));
bindSlider('rain-audio-volume', (value) => value.toFixed(2), (value) => oceanScene.setRainAudioVolume(value));
bindSlider('snow-intensity', (value) => value.toFixed(2), (value) => oceanScene.setSnowIntensity(value));
bindSlider('snow-speed', (value) => value.toFixed(2), (value) => oceanScene.setSnowSpeed(value));
bindSlider('lightning-intensity', (value) => value.toFixed(2), (value) => oceanScene.setLightningIntensity(value));
bindCheckbox('rain-enabled', (value) => oceanScene.setRainEnabled(value));
bindCheckbox('rain-audio-enabled', (value) => oceanScene.setRainAudioEnabled(value));
bindCheckbox('snow-enabled', (value) => oceanScene.setSnowEnabled(value));
bindCheckbox('lightning-enabled', (value) => oceanScene.setLightningEnabled(value));
}