diff --git a/index.html b/index.html index b75b627..f15e7e6 100644 --- a/index.html +++ b/index.html @@ -336,6 +336,21 @@ + +
+
雷闪
+
+ + +
+
+
+ + +
0.75
+
+
+
FPS: 60
diff --git a/src/OceanScene.js b/src/OceanScene.js index 79b6838..aa6faba 100644 --- a/src/OceanScene.js +++ b/src/OceanScene.js @@ -26,6 +26,7 @@ export class OceanScene { this.renderTarget = null; this.sunLight = null; this.vegetationFillLight = null; + this.lightningLight = null; this.composer = null; this.bloomPass = null; this.rainPass = null; @@ -57,6 +58,8 @@ export class OceanScene { rainVeilIntensity: 1.15, rainDropSize: 1.0, rainSpeed: 1.0, + lightningEnabled: false, + lightningIntensity: 0.75, mieCoefficient: 0.005, mieDirectionalG: 0.8 }; @@ -64,6 +67,9 @@ export class OceanScene { this.clock = new THREE.Clock(); this.frameCount = 0; this.lastTime = performance.now(); + this.lightningFlash = 0; + this.lightningBurstEnd = 0; + this.nextLightningAt = 0; } async init() { @@ -142,6 +148,11 @@ export class OceanScene { this.vegetationFillLight = new THREE.DirectionalLight(0xffb06a, 0.95); this.vegetationFillLight.castShadow = false; this.scene.add(this.vegetationFillLight); + + this.lightningLight = new THREE.DirectionalLight(0xddeeff, 0); + this.lightningLight.castShadow = false; + this.lightningLight.position.set(-120, 180, 40); + this.scene.add(this.lightningLight); } initPostProcessing() { @@ -1002,6 +1013,74 @@ export class OceanScene { } } + setLightningEnabled(value) { + this.params.lightningEnabled = value; + if (!value) { + this.lightningFlash = 0; + this.lightningBurstEnd = 0; + this.nextLightningAt = 0; + this.applyLightningState(0); + } + } + + setLightningIntensity(value) { + this.params.lightningIntensity = value; + } + + scheduleNextLightning(time) { + const rainActivity = Math.max(this.params.rainVeilIntensity, this.params.rainScreenIntensity); + const densityBias = THREE.MathUtils.clamp(rainActivity / 1.5, 0, 1); + const delay = THREE.MathUtils.lerp(7.5, 3.0, densityBias) + Math.random() * THREE.MathUtils.lerp(8.0, 4.0, densityBias); + this.nextLightningAt = time + delay; + } + + updateLightning(time) { + if (!this.params.lightningEnabled) return; + + if (this.nextLightningAt === 0) { + this.scheduleNextLightning(time); + } + + if (time >= this.nextLightningAt && time >= this.lightningBurstEnd) { + const burstLength = 0.18 + Math.random() * 0.16; + this.lightningBurstEnd = time + burstLength; + this.lightningLight.position.set( + THREE.MathUtils.randFloat(-220, 220), + THREE.MathUtils.randFloat(140, 260), + THREE.MathUtils.randFloat(-120, 120) + ); + this.scheduleNextLightning(time); + } + + if (time < this.lightningBurstEnd) { + const burstProgress = 1.0 - (this.lightningBurstEnd - time) / Math.max(this.lightningBurstEnd - (this.lightningBurstEnd - 0.34), 0.0001); + const pulseA = Math.max(0, Math.sin((time + 0.13) * 37.0)); + const pulseB = Math.max(0, Math.sin((time + 0.04) * 83.0)); + const envelope = Math.exp(-burstProgress * 5.5); + const flash = (pulseA * 0.75 + pulseB * 0.45 + 0.25) * envelope * this.params.lightningIntensity; + this.lightningFlash = Math.max(this.lightningFlash * 0.72, flash); + } else { + this.lightningFlash *= 0.82; + if (this.lightningFlash < 0.002) this.lightningFlash = 0; + } + + this.applyLightningState(this.lightningFlash); + } + + applyLightningState(flash) { + if (this.lightningLight) { + this.lightningLight.intensity = flash * 5.5; + } + + if (this.renderer) { + this.renderer.toneMappingExposure = this.params.exposure * (1.0 + flash * 1.6); + } + + if (this.bloomPass) { + this.bloomPass.strength = this.params.bloomStrength + flash * 0.35; + } + } + updateClouds() { if (!this.cloudGroup) return; @@ -1009,6 +1088,8 @@ export class OceanScene { const warmCloud = new THREE.Color(0xdab188); const dayCloud = new THREE.Color(0xd1dbe6); const cloudColor = warmCloud.lerp(dayCloud, sunMix); + const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.85, 0, 1); + cloudColor.lerp(new THREE.Color(0xe9f3ff), lightningMix); for (const layer of this.cloudLayers) { const coverageFactor = 0.15 + this.params.cloudCoverage * 1.15; @@ -1023,6 +1104,10 @@ export class OceanScene { updateFog() { const { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor } = this.getAtmosphereColors(); const fogDensity = THREE.MathUtils.lerp(0.00015, 0.0018, this.params.fogDensity); + const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.75, 0, 1); + fogColor.lerp(new THREE.Color(0xdbe8f5), lightningMix); + horizonColor.lerp(new THREE.Color(0xe5eef9), lightningMix); + skyBlendColor.lerp(new THREE.Color(0xdbe7f3), lightningMix); if (this.scene.fog) { this.scene.fog.color.copy(fogColor); @@ -1077,6 +1162,7 @@ export class OceanScene { requestAnimationFrame(() => this.animate()); const time = this.clock.getElapsedTime(); + this.updateLightning(time); if (this.water) { this.water.material.uniforms['time'].value += 1.0 / 60.0; @@ -1108,6 +1194,11 @@ export class OceanScene { this.vegetationSystem.update(time); } + if (this.lightningFlash > 0.001) { + this.updateClouds(); + this.updateFog(); + } + if (this.rainPass) { this.rainPass.material.uniforms.time.value = time; } diff --git a/src/main.js b/src/main.js index 7ab6508..f19c042 100644 --- a/src/main.js +++ b/src/main.js @@ -68,7 +68,9 @@ function setupControls(oceanScene) { bindSlider('rain-veil-intensity', (value) => value.toFixed(2), (value) => oceanScene.setRainVeilIntensity(value)); bindSlider('rain-drop-size', (value) => value.toFixed(2), (value) => oceanScene.setRainDropSize(value)); bindSlider('rain-speed', (value) => value.toFixed(2), (value) => oceanScene.setRainSpeed(value)); + bindSlider('lightning-intensity', (value) => value.toFixed(2), (value) => oceanScene.setLightningIntensity(value)); bindCheckbox('rain-enabled', (value) => oceanScene.setRainEnabled(value)); + bindCheckbox('lightning-enabled', (value) => oceanScene.setLightningEnabled(value)); } main().catch(console.error);