下雪
This commit is contained in:
20
index.html
20
index.html
@@ -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">
|
||||
|
||||
@@ -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() {
|
||||
@@ -442,6 +453,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();
|
||||
this.sky.scale.setScalar(10000);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user