From 77664bce52a91566cda29df2557a83ed6c656036 Mon Sep 17 00:00:00 2001 From: como Date: Thu, 26 Mar 2026 16:13:49 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=84=E8=AE=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OceanScene.js | 62 ++++++++++--------- src/main.js | 80 ++++++++++++++++-------- src/weatherPresets.js | 139 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 55 deletions(-) create mode 100644 src/weatherPresets.js diff --git a/src/OceanScene.js b/src/OceanScene.js index b0c4fd9..4e54a91 100644 --- a/src/OceanScene.js +++ b/src/OceanScene.js @@ -9,6 +9,7 @@ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import Stats from 'three/addons/libs/stats.module.js'; import { TerrainGenerator } from './TerrainGenerator.js'; import { VegetationSystem } from './VegetationSystem.js'; +import { DEFAULT_SCENE_PARAMS } from './weatherPresets.js'; const RAIN_AUDIO_URL = '/audio/rain-calming.mp3'; const THUNDER_AUDIO_URL = '/audio/thunder-distant.mp3'; @@ -53,36 +54,7 @@ export class OceanScene { this.thunderAudioIndex = 0; this.scheduledThunder = []; - this.params = { - elevation: 2, - azimuth: 180, - exposure: 0.1, - turbidity: 10, - rayleigh: 2, - bloomStrength: 0.1, - bloomRadius: 0, - bloomThreshold: 0, - cloudCoverage: 0.4, - cloudDensity: 0.5, - cloudElevation: 0.5, - fogDensity: 0.42, - fogHeight: 0.32, - fogRange: 0.55, - rainEnabled: false, - rainScreenIntensity: 0.41, - rainVeilIntensity: 1.15, - rainDropSize: 1.0, - rainSpeed: 1.0, - rainAudioEnabled: true, - rainAudioVolume: 0.35, - snowEnabled: false, - snowIntensity: 0.65, - snowSpeed: 0.85, - lightningEnabled: true, - lightningIntensity: 0.75, - mieCoefficient: 0.005, - mieDirectionalG: 0.8 - }; + this.params = { ...DEFAULT_SCENE_PARAMS }; this.clock = new THREE.Clock(); this.lightningFlash = 0; @@ -1321,6 +1293,36 @@ export class OceanScene { this.params.lightningIntensity = value; } + applyParams(nextParams = {}) { + const mergedParams = { ...DEFAULT_SCENE_PARAMS, ...nextParams }; + + this.setSunElevation(mergedParams.elevation); + this.setSunAzimuth(mergedParams.azimuth); + this.setExposure(mergedParams.exposure); + this.setTurbidity(mergedParams.turbidity); + this.setRayleigh(mergedParams.rayleigh); + this.setBloomStrength(mergedParams.bloomStrength); + this.setBloomRadius(mergedParams.bloomRadius); + this.setCloudCoverage(mergedParams.cloudCoverage); + this.setCloudDensity(mergedParams.cloudDensity); + this.setCloudElevation(mergedParams.cloudElevation); + this.setFogDensity(mergedParams.fogDensity); + this.setFogHeight(mergedParams.fogHeight); + this.setFogRange(mergedParams.fogRange); + this.setRainScreenIntensity(mergedParams.rainScreenIntensity); + this.setRainVeilIntensity(mergedParams.rainVeilIntensity); + this.setRainDropSize(mergedParams.rainDropSize); + this.setRainSpeed(mergedParams.rainSpeed); + this.setRainAudioVolume(mergedParams.rainAudioVolume); + this.setRainAudioEnabled(mergedParams.rainAudioEnabled); + this.setSnowIntensity(mergedParams.snowIntensity); + this.setSnowSpeed(mergedParams.snowSpeed); + this.setSnowEnabled(mergedParams.snowEnabled); + this.setLightningIntensity(mergedParams.lightningIntensity); + this.setLightningEnabled(mergedParams.lightningEnabled); + this.setRainEnabled(mergedParams.rainEnabled); + } + scheduleNextLightning(time) { const rainActivity = Math.max(this.params.rainVeilIntensity, this.params.rainScreenIntensity); const densityBias = THREE.MathUtils.clamp(rainActivity / 1.5, 0, 1); diff --git a/src/main.js b/src/main.js index 743469c..227118d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { OceanScene } from './OceanScene.js'; +import { WEATHER_PRESETS } from './weatherPresets.js'; async function main() { const container = document.getElementById('container'); @@ -37,6 +38,11 @@ function setupControls(oceanScene) { gui.domElement.style.zIndex = '120'; const params = oceanScene.params; + const controllers = []; + const presetState = { 当前天气: 'default' }; + const presetOptions = Object.fromEntries( + Object.entries(WEATHER_PRESETS).map(([key, preset]) => [preset.label, key]) + ); const presetKeys = [ 'elevation', 'azimuth', @@ -119,44 +125,68 @@ function setupControls(oceanScene) { } }; + const markPresetCustom = () => { + if (presetState.当前天气 !== 'custom') { + presetState.当前天气 = 'custom'; + presetController.updateDisplay(); + } + }; + const refreshControllers = () => { + controllers.forEach((controller) => controller.updateDisplay()); + }; + const bindController = (controller, applyValue) => { + controllers.push(controller); + controller.onChange((value) => { + applyValue(value); + markPresetCustom(); + }); + return controller; + }; + + const presetController = gui.add(presetState, '当前天气', { ...presetOptions, 自定义: 'custom' }).name('天气预设'); + presetController.onChange((presetKey) => { + if (presetKey === 'custom') return; + oceanScene.applyParams(WEATHER_PRESETS[presetKey].params); + refreshControllers(); + }); gui.add(exportActions, '导出预设'); const skyFolder = gui.addFolder('天空'); - skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度').onChange((value) => oceanScene.setSunElevation(value)); - skyFolder.add(params, 'azimuth', -180, 180, 0.1).name('太阳方位').onChange((value) => oceanScene.setSunAzimuth(value)); - skyFolder.add(params, 'exposure', 0, 1, 0.01).name('曝光度').onChange((value) => oceanScene.setExposure(value)); - skyFolder.add(params, 'turbidity', 1, 20, 0.1).name('浑浊度').onChange((value) => oceanScene.setTurbidity(value)); - skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射').onChange((value) => oceanScene.setRayleigh(value)); + bindController(skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度'), (value) => oceanScene.setSunElevation(value)); + bindController(skyFolder.add(params, 'azimuth', -180, 180, 0.1).name('太阳方位'), (value) => oceanScene.setSunAzimuth(value)); + bindController(skyFolder.add(params, 'exposure', 0, 1, 0.01).name('曝光度'), (value) => oceanScene.setExposure(value)); + bindController(skyFolder.add(params, 'turbidity', 1, 20, 0.1).name('浑浊度'), (value) => oceanScene.setTurbidity(value)); + bindController(skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射'), (value) => oceanScene.setRayleigh(value)); const bloomFolder = gui.addFolder('泛光'); - bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度').onChange((value) => oceanScene.setBloomStrength(value)); - bloomFolder.add(params, 'bloomRadius', 0, 3, 0.01).name('扩散').onChange((value) => oceanScene.setBloomRadius(value)); + bindController(bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度'), (value) => oceanScene.setBloomStrength(value)); + bindController(bloomFolder.add(params, 'bloomRadius', 0, 3, 0.01).name('扩散'), (value) => oceanScene.setBloomRadius(value)); const cloudFolder = gui.addFolder('云层'); - cloudFolder.add(params, 'cloudCoverage', 0, 1, 0.01).name('覆盖度').onChange((value) => oceanScene.setCloudCoverage(value)); - cloudFolder.add(params, 'cloudDensity', 0, 1, 0.01).name('密度').onChange((value) => oceanScene.setCloudDensity(value)); - cloudFolder.add(params, 'cloudElevation', 0, 1, 0.01).name('高度').onChange((value) => oceanScene.setCloudElevation(value)); + bindController(cloudFolder.add(params, 'cloudCoverage', 0, 1, 0.01).name('覆盖度'), (value) => oceanScene.setCloudCoverage(value)); + bindController(cloudFolder.add(params, 'cloudDensity', 0, 1, 0.01).name('密度'), (value) => oceanScene.setCloudDensity(value)); + bindController(cloudFolder.add(params, 'cloudElevation', 0, 1, 0.01).name('高度'), (value) => oceanScene.setCloudElevation(value)); const fogFolder = gui.addFolder('雾气'); - fogFolder.add(params, 'fogDensity', 0, 1, 0.01).name('浓度').onChange((value) => oceanScene.setFogDensity(value)); - fogFolder.add(params, 'fogHeight', 0, 1, 0.01).name('高度').onChange((value) => oceanScene.setFogHeight(value)); - fogFolder.add(params, 'fogRange', 0, 1, 0.01).name('范围').onChange((value) => oceanScene.setFogRange(value)); + bindController(fogFolder.add(params, 'fogDensity', 0, 1, 0.01).name('浓度'), (value) => oceanScene.setFogDensity(value)); + bindController(fogFolder.add(params, 'fogHeight', 0, 1, 0.01).name('高度'), (value) => oceanScene.setFogHeight(value)); + bindController(fogFolder.add(params, 'fogRange', 0, 1, 0.01).name('范围'), (value) => oceanScene.setFogRange(value)); const rainFolder = gui.addFolder('雨效'); - rainFolder.add(params, 'rainEnabled').name('启用雨效').onChange((value) => oceanScene.setRainEnabled(value)); - rainFolder.add(params, 'rainScreenIntensity', 0, 1.5, 0.01).name('屏幕雨滴').onChange((value) => oceanScene.setRainScreenIntensity(value)); - rainFolder.add(params, 'rainVeilIntensity', 0, 1.5, 0.01).name('雨线强度').onChange((value) => oceanScene.setRainVeilIntensity(value)); - rainFolder.add(params, 'rainDropSize', 0.4, 1.8, 0.01).name('雨滴尺寸').onChange((value) => oceanScene.setRainDropSize(value)); - rainFolder.add(params, 'rainSpeed', 0.2, 2.5, 0.01).name('速度').onChange((value) => oceanScene.setRainSpeed(value)); - rainFolder.add(params, 'rainAudioEnabled').name('启用雨声').onChange((value) => oceanScene.setRainAudioEnabled(value)); - rainFolder.add(params, 'rainAudioVolume', 0, 1, 0.01).name('雨声音量').onChange((value) => oceanScene.setRainAudioVolume(value)); - rainFolder.add(params, 'lightningEnabled').name('启用雷闪').onChange((value) => oceanScene.setLightningEnabled(value)); - rainFolder.add(params, 'lightningIntensity', 0, 1.5, 0.01).name('雷闪强度').onChange((value) => oceanScene.setLightningIntensity(value)); + bindController(rainFolder.add(params, 'rainEnabled').name('启用雨效'), (value) => oceanScene.setRainEnabled(value)); + bindController(rainFolder.add(params, 'rainScreenIntensity', 0, 1.5, 0.01).name('屏幕雨滴'), (value) => oceanScene.setRainScreenIntensity(value)); + bindController(rainFolder.add(params, 'rainVeilIntensity', 0.5, 2.5, 0.01).name('雨线强度'), (value) => oceanScene.setRainVeilIntensity(value)); + bindController(rainFolder.add(params, 'rainDropSize', 0.4, 1.8, 0.01).name('雨滴尺寸'), (value) => oceanScene.setRainDropSize(value)); + bindController(rainFolder.add(params, 'rainSpeed', 0.2, 2.5, 0.01).name('速度'), (value) => oceanScene.setRainSpeed(value)); + bindController(rainFolder.add(params, 'rainAudioEnabled').name('启用雨声'), (value) => oceanScene.setRainAudioEnabled(value)); + bindController(rainFolder.add(params, 'rainAudioVolume', 0, 1, 0.01).name('雨声音量'), (value) => oceanScene.setRainAudioVolume(value)); + bindController(rainFolder.add(params, 'lightningEnabled').name('启用雷闪'), (value) => oceanScene.setLightningEnabled(value)); + bindController(rainFolder.add(params, 'lightningIntensity', 0, 1.5, 0.01).name('雷闪强度'), (value) => oceanScene.setLightningIntensity(value)); const snowFolder = gui.addFolder('雪效'); - snowFolder.add(params, 'snowEnabled').name('启用降雪').onChange((value) => oceanScene.setSnowEnabled(value)); - snowFolder.add(params, 'snowIntensity', 0, 1.5, 0.01).name('雪量').onChange((value) => oceanScene.setSnowIntensity(value)); - snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度').onChange((value) => oceanScene.setSnowSpeed(value)); + bindController(snowFolder.add(params, 'snowEnabled').name('启用降雪'), (value) => oceanScene.setSnowEnabled(value)); + bindController(snowFolder.add(params, 'snowIntensity', 0, 1.5, 0.01).name('雪量'), (value) => oceanScene.setSnowIntensity(value)); + bindController(snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度'), (value) => oceanScene.setSnowSpeed(value)); gui.close(); } diff --git a/src/weatherPresets.js b/src/weatherPresets.js new file mode 100644 index 0000000..aad451f --- /dev/null +++ b/src/weatherPresets.js @@ -0,0 +1,139 @@ +export const DEFAULT_SCENE_PARAMS = { + elevation: 2, + azimuth: 180, + exposure: 0.1, + turbidity: 10, + rayleigh: 2, + bloomStrength: 0.1, + bloomRadius: 0, + bloomThreshold: 0, + cloudCoverage: 0.4, + cloudDensity: 0.5, + cloudElevation: 0.5, + fogDensity: 0.42, + fogHeight: 0.32, + fogRange: 0.55, + rainEnabled: false, + rainScreenIntensity: 0.41, + rainVeilIntensity: 1.15, + rainDropSize: 1.0, + rainSpeed: 1.0, + rainAudioEnabled: true, + rainAudioVolume: 0.35, + snowEnabled: false, + snowIntensity: 0.65, + snowSpeed: 0.85, + lightningEnabled: true, + lightningIntensity: 0.75, + mieCoefficient: 0.005, + mieDirectionalG: 0.8 +}; + +export const WEATHER_PRESETS = { + default: { + label: '默认', + params: { + ...DEFAULT_SCENE_PARAMS + } + }, + clear: { + label: '日出', + params: { + ...DEFAULT_SCENE_PARAMS, + "elevation": 0, + "azimuth": 0, + "exposure": 0.61, + "turbidity": 1, + "rayleigh": 3.21, + "bloomStrength": 0.24, + "bloomRadius": 0.42, + "cloudCoverage": 0.4, + "cloudDensity": 0.18, + "cloudElevation": 0.98, + "fogDensity": 0, + "fogHeight": 1, + "fogRange": 0.02, + "rainEnabled": false, + "rainScreenIntensity": 0.41, + "rainVeilIntensity": 1.15, + "rainDropSize": 1, + "rainSpeed": 1, + "rainAudioEnabled": true, + "rainAudioVolume": 0.35, + "snowEnabled": false, + "snowIntensity": 0.65, + "snowSpeed": 0.85, + "lightningEnabled": true, + "lightningIntensity": 0.75 + + } + }, + rainy: { + label: '下雨', + params: { + ...DEFAULT_SCENE_PARAMS, + exposure: 0.08, + bloomStrength: 0.16, + bloomRadius: 0.24, + cloudCoverage: 0.78, + cloudDensity: 0.82, + cloudElevation: 0.42, + fogDensity: 0.58, + fogHeight: 0.36, + fogRange: 0.72, + rainEnabled: true, + rainScreenIntensity: 0.38, + rainVeilIntensity: 1.25, + rainDropSize: 0.46, + rainSpeed: 1.18, + rainAudioEnabled: true, + rainAudioVolume: 0.35, + lightningEnabled: true, + lightningIntensity: 0.75, + snowEnabled: false + } + }, + storm: { + label: '暴雨雷暴', + params: { + ...DEFAULT_SCENE_PARAMS, + exposure: 0.065, + bloomStrength: 0.22, + bloomRadius: 0.35, + cloudCoverage: 0.92, + cloudDensity: 0.94, + cloudElevation: 0.36, + fogDensity: 0.7, + fogHeight: 0.42, + fogRange: 0.82, + rainEnabled: true, + rainScreenIntensity: 0.72, + rainVeilIntensity: 1.35, + rainDropSize: 1.14, + rainSpeed: 1.34, + rainAudioEnabled: true, + rainAudioVolume: 0.42, + lightningEnabled: true, + lightningIntensity: 1.0, + snowEnabled: false + } + }, + snow: { + label: '降雪', + params: { + ...DEFAULT_SCENE_PARAMS, + exposure: 0.11, + bloomStrength: 0.08, + cloudCoverage: 0.72, + cloudDensity: 0.7, + fogDensity: 0.52, + fogHeight: 0.4, + fogRange: 0.68, + rainEnabled: false, + lightningEnabled: false, + snowEnabled: true, + snowIntensity: 1.1, + snowSpeed: 1.2 + } + } +};