This commit is contained in:
2026-03-26 16:13:49 +08:00
parent e7d4267f60
commit 77664bce52
3 changed files with 226 additions and 55 deletions

View File

@@ -9,6 +9,7 @@ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import Stats from 'three/addons/libs/stats.module.js'; import Stats from 'three/addons/libs/stats.module.js';
import { TerrainGenerator } from './TerrainGenerator.js'; import { TerrainGenerator } from './TerrainGenerator.js';
import { VegetationSystem } from './VegetationSystem.js'; import { VegetationSystem } from './VegetationSystem.js';
import { DEFAULT_SCENE_PARAMS } from './weatherPresets.js';
const RAIN_AUDIO_URL = '/audio/rain-calming.mp3'; const RAIN_AUDIO_URL = '/audio/rain-calming.mp3';
const THUNDER_AUDIO_URL = '/audio/thunder-distant.mp3'; const THUNDER_AUDIO_URL = '/audio/thunder-distant.mp3';
@@ -53,36 +54,7 @@ export class OceanScene {
this.thunderAudioIndex = 0; this.thunderAudioIndex = 0;
this.scheduledThunder = []; this.scheduledThunder = [];
this.params = { this.params = { ...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
};
this.clock = new THREE.Clock(); this.clock = new THREE.Clock();
this.lightningFlash = 0; this.lightningFlash = 0;
@@ -1321,6 +1293,36 @@ export class OceanScene {
this.params.lightningIntensity = value; 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) { scheduleNextLightning(time) {
const rainActivity = Math.max(this.params.rainVeilIntensity, this.params.rainScreenIntensity); const rainActivity = Math.max(this.params.rainVeilIntensity, this.params.rainScreenIntensity);
const densityBias = THREE.MathUtils.clamp(rainActivity / 1.5, 0, 1); const densityBias = THREE.MathUtils.clamp(rainActivity / 1.5, 0, 1);

View File

@@ -1,5 +1,6 @@
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OceanScene } from './OceanScene.js'; import { OceanScene } from './OceanScene.js';
import { WEATHER_PRESETS } from './weatherPresets.js';
async function main() { async function main() {
const container = document.getElementById('container'); const container = document.getElementById('container');
@@ -37,6 +38,11 @@ function setupControls(oceanScene) {
gui.domElement.style.zIndex = '120'; gui.domElement.style.zIndex = '120';
const params = oceanScene.params; 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 = [ const presetKeys = [
'elevation', 'elevation',
'azimuth', '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, '导出预设'); gui.add(exportActions, '导出预设');
const skyFolder = gui.addFolder('天空'); const skyFolder = gui.addFolder('天空');
skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度').onChange((value) => oceanScene.setSunElevation(value)); bindController(skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度'), (value) => oceanScene.setSunElevation(value));
skyFolder.add(params, 'azimuth', -180, 180, 0.1).name('太阳方位').onChange((value) => oceanScene.setSunAzimuth(value)); bindController(skyFolder.add(params, 'azimuth', -180, 180, 0.1).name('太阳方位'), (value) => oceanScene.setSunAzimuth(value));
skyFolder.add(params, 'exposure', 0, 1, 0.01).name('曝光度').onChange((value) => oceanScene.setExposure(value)); bindController(skyFolder.add(params, 'exposure', 0, 1, 0.01).name('曝光度'), (value) => oceanScene.setExposure(value));
skyFolder.add(params, 'turbidity', 1, 20, 0.1).name('浑浊度').onChange((value) => oceanScene.setTurbidity(value)); bindController(skyFolder.add(params, 'turbidity', 1, 20, 0.1).name('浑浊度'), (value) => oceanScene.setTurbidity(value));
skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射').onChange((value) => oceanScene.setRayleigh(value)); bindController(skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射'), (value) => oceanScene.setRayleigh(value));
const bloomFolder = gui.addFolder('泛光'); const bloomFolder = gui.addFolder('泛光');
bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度').onChange((value) => oceanScene.setBloomStrength(value)); bindController(bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度'), (value) => oceanScene.setBloomStrength(value));
bloomFolder.add(params, 'bloomRadius', 0, 3, 0.01).name('扩散').onChange((value) => oceanScene.setBloomRadius(value)); bindController(bloomFolder.add(params, 'bloomRadius', 0, 3, 0.01).name('扩散'), (value) => oceanScene.setBloomRadius(value));
const cloudFolder = gui.addFolder('云层'); const cloudFolder = gui.addFolder('云层');
cloudFolder.add(params, 'cloudCoverage', 0, 1, 0.01).name('覆盖度').onChange((value) => oceanScene.setCloudCoverage(value)); bindController(cloudFolder.add(params, 'cloudCoverage', 0, 1, 0.01).name('覆盖度'), (value) => oceanScene.setCloudCoverage(value));
cloudFolder.add(params, 'cloudDensity', 0, 1, 0.01).name('密度').onChange((value) => oceanScene.setCloudDensity(value)); bindController(cloudFolder.add(params, 'cloudDensity', 0, 1, 0.01).name('密度'), (value) => oceanScene.setCloudDensity(value));
cloudFolder.add(params, 'cloudElevation', 0, 1, 0.01).name('高度').onChange((value) => oceanScene.setCloudElevation(value)); bindController(cloudFolder.add(params, 'cloudElevation', 0, 1, 0.01).name('高度'), (value) => oceanScene.setCloudElevation(value));
const fogFolder = gui.addFolder('雾气'); const fogFolder = gui.addFolder('雾气');
fogFolder.add(params, 'fogDensity', 0, 1, 0.01).name('浓度').onChange((value) => oceanScene.setFogDensity(value)); bindController(fogFolder.add(params, 'fogDensity', 0, 1, 0.01).name('浓度'), (value) => oceanScene.setFogDensity(value));
fogFolder.add(params, 'fogHeight', 0, 1, 0.01).name('高度').onChange((value) => oceanScene.setFogHeight(value)); bindController(fogFolder.add(params, 'fogHeight', 0, 1, 0.01).name('高度'), (value) => oceanScene.setFogHeight(value));
fogFolder.add(params, 'fogRange', 0, 1, 0.01).name('范围').onChange((value) => oceanScene.setFogRange(value)); bindController(fogFolder.add(params, 'fogRange', 0, 1, 0.01).name('范围'), (value) => oceanScene.setFogRange(value));
const rainFolder = gui.addFolder('雨效'); const rainFolder = gui.addFolder('雨效');
rainFolder.add(params, 'rainEnabled').name('启用雨效').onChange((value) => oceanScene.setRainEnabled(value)); bindController(rainFolder.add(params, 'rainEnabled').name('启用雨效'), (value) => oceanScene.setRainEnabled(value));
rainFolder.add(params, 'rainScreenIntensity', 0, 1.5, 0.01).name('屏幕雨滴').onChange((value) => oceanScene.setRainScreenIntensity(value)); bindController(rainFolder.add(params, 'rainScreenIntensity', 0, 1.5, 0.01).name('屏幕雨滴'), (value) => oceanScene.setRainScreenIntensity(value));
rainFolder.add(params, 'rainVeilIntensity', 0, 1.5, 0.01).name('雨线强度').onChange((value) => oceanScene.setRainVeilIntensity(value)); bindController(rainFolder.add(params, 'rainVeilIntensity', 0.5, 2.5, 0.01).name('雨线强度'), (value) => oceanScene.setRainVeilIntensity(value));
rainFolder.add(params, 'rainDropSize', 0.4, 1.8, 0.01).name('雨滴尺寸').onChange((value) => oceanScene.setRainDropSize(value)); bindController(rainFolder.add(params, 'rainDropSize', 0.4, 1.8, 0.01).name('雨滴尺寸'), (value) => oceanScene.setRainDropSize(value));
rainFolder.add(params, 'rainSpeed', 0.2, 2.5, 0.01).name('速度').onChange((value) => oceanScene.setRainSpeed(value)); bindController(rainFolder.add(params, 'rainSpeed', 0.2, 2.5, 0.01).name('速度'), (value) => oceanScene.setRainSpeed(value));
rainFolder.add(params, 'rainAudioEnabled').name('启用雨声').onChange((value) => oceanScene.setRainAudioEnabled(value)); bindController(rainFolder.add(params, 'rainAudioEnabled').name('启用雨声'), (value) => oceanScene.setRainAudioEnabled(value));
rainFolder.add(params, 'rainAudioVolume', 0, 1, 0.01).name('雨声音量').onChange((value) => oceanScene.setRainAudioVolume(value)); bindController(rainFolder.add(params, 'rainAudioVolume', 0, 1, 0.01).name('雨声音量'), (value) => oceanScene.setRainAudioVolume(value));
rainFolder.add(params, 'lightningEnabled').name('启用雷闪').onChange((value) => oceanScene.setLightningEnabled(value)); bindController(rainFolder.add(params, 'lightningEnabled').name('启用雷闪'), (value) => oceanScene.setLightningEnabled(value));
rainFolder.add(params, 'lightningIntensity', 0, 1.5, 0.01).name('雷闪强度').onChange((value) => oceanScene.setLightningIntensity(value)); bindController(rainFolder.add(params, 'lightningIntensity', 0, 1.5, 0.01).name('雷闪强度'), (value) => oceanScene.setLightningIntensity(value));
const snowFolder = gui.addFolder('雪效'); const snowFolder = gui.addFolder('雪效');
snowFolder.add(params, 'snowEnabled').name('启用降雪').onChange((value) => oceanScene.setSnowEnabled(value)); bindController(snowFolder.add(params, 'snowEnabled').name('启用降雪'), (value) => oceanScene.setSnowEnabled(value));
snowFolder.add(params, 'snowIntensity', 0, 1.5, 0.01).name('雪量').onChange((value) => oceanScene.setSnowIntensity(value)); bindController(snowFolder.add(params, 'snowIntensity', 0, 1.5, 0.01).name('雪量'), (value) => oceanScene.setSnowIntensity(value));
snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度').onChange((value) => oceanScene.setSnowSpeed(value)); bindController(snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度'), (value) => oceanScene.setSnowSpeed(value));
gui.close(); gui.close();
} }

139
src/weatherPresets.js Normal file
View File

@@ -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
}
}
};