diff --git a/index.html b/index.html
index aa9d6f4..db58eb6 100644
--- a/index.html
+++ b/index.html
@@ -348,6 +348,26 @@
+
雷闪
diff --git a/src/OceanScene.js b/src/OceanScene.js
index 34c14a3..6541fc5 100644
--- a/src/OceanScene.js
+++ b/src/OceanScene.js
@@ -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) {
diff --git a/src/main.js b/src/main.js
index 121cb94..efc94c0 100644
--- a/src/main.js
+++ b/src/main.js
@@ -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));
}