加入导出功能

This commit is contained in:
2026-03-26 14:06:08 +08:00
parent f3581443d6
commit e7d4267f60
3 changed files with 103 additions and 31 deletions

View File

@@ -60,17 +60,6 @@
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
#stats {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 15px;
border-radius: 8px;
color: white;
font-size: 12px;
z-index: 100;
}
</style> </style>
</head> </head>
<body> <body>
@@ -82,8 +71,6 @@
<div id="container"></div> <div id="container"></div>
<div id="stats">FPS: <span id="fps">60</span></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -6,6 +6,7 @@ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.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';
@@ -35,6 +36,7 @@ export class OceanScene {
this.bloomPass = null; this.bloomPass = null;
this.rainPass = null; this.rainPass = null;
this.snowPass = null; this.snowPass = null;
this.stats = null;
this.cloudGroup = null; this.cloudGroup = null;
this.cloudMaterials = []; this.cloudMaterials = [];
this.cloudLayers = []; this.cloudLayers = [];
@@ -83,8 +85,6 @@ export class OceanScene {
}; };
this.clock = new THREE.Clock(); this.clock = new THREE.Clock();
this.frameCount = 0;
this.lastTime = performance.now();
this.lightningFlash = 0; this.lightningFlash = 0;
this.lightningLocalFlash = 0; this.lightningLocalFlash = 0;
this.lightningBurstEnd = 0; this.lightningBurstEnd = 0;
@@ -94,6 +94,7 @@ export class OceanScene {
async init() { async init() {
this.initRenderer(); this.initRenderer();
this.initStats();
this.initScene(); this.initScene();
this.initCamera(); this.initCamera();
this.initControls(); this.initControls();
@@ -123,6 +124,18 @@ export class OceanScene {
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.container.appendChild(this.renderer.domElement); this.container.appendChild(this.renderer.domElement);
} }
initStats() {
this.stats = new Stats();
this.stats.showPanel(0);
this.stats.dom.style.position = 'fixed';
this.stats.dom.style.left = '0';
this.stats.dom.style.top = '0';
this.stats.dom.style.bottom = 'auto';
this.stats.dom.style.margin = '0';
this.stats.dom.style.zIndex = '120';
this.container.appendChild(this.stats.dom);
}
initScene() { initScene() {
this.scene = new THREE.Scene(); this.scene = new THREE.Scene();
@@ -1535,6 +1548,7 @@ export class OceanScene {
animate() { animate() {
requestAnimationFrame(() => this.animate()); requestAnimationFrame(() => this.animate());
this.stats?.begin();
const time = this.clock.getElapsedTime(); const time = this.clock.getElapsedTime();
this.updateLightning(time); this.updateLightning(time);
@@ -1589,18 +1603,7 @@ export class OceanScene {
} else { } else {
this.renderer.render(this.scene, this.camera); this.renderer.render(this.scene, this.camera);
} }
this.stats?.end();
this.frameCount++;
const currentTime = performance.now();
if (currentTime - this.lastTime >= 1000) {
const fps = Math.round(this.frameCount * 1000 / (currentTime - this.lastTime));
const fpsElement = document.getElementById('fps');
if (fpsElement) {
fpsElement.textContent = fps;
}
this.frameCount = 0;
this.lastTime = currentTime;
}
} }
hideLoading() { hideLoading() {

View File

@@ -37,6 +37,89 @@ function setupControls(oceanScene) {
gui.domElement.style.zIndex = '120'; gui.domElement.style.zIndex = '120';
const params = oceanScene.params; const params = oceanScene.params;
const presetKeys = [
'elevation',
'azimuth',
'exposure',
'turbidity',
'rayleigh',
'bloomStrength',
'bloomRadius',
'cloudCoverage',
'cloudDensity',
'cloudElevation',
'fogDensity',
'fogHeight',
'fogRange',
'rainEnabled',
'rainScreenIntensity',
'rainVeilIntensity',
'rainDropSize',
'rainSpeed',
'rainAudioEnabled',
'rainAudioVolume',
'snowEnabled',
'snowIntensity',
'snowSpeed',
'lightningEnabled',
'lightningIntensity'
];
const presetComments = {
elevation: '太阳高度角',
azimuth: '太阳方位角',
exposure: '场景曝光度',
turbidity: '天空浑浊度',
rayleigh: '瑞利散射强度',
bloomStrength: '泛光强度',
bloomRadius: '泛光扩散范围',
cloudCoverage: '云层覆盖度',
cloudDensity: '云层密度',
cloudElevation: '云层高度',
fogDensity: '雾气浓度',
fogHeight: '雾气高度',
fogRange: '雾气范围',
rainEnabled: '是否启用雨效',
rainScreenIntensity: '屏幕雨滴强度',
rainVeilIntensity: '雨线强度',
rainDropSize: '雨滴尺寸',
rainSpeed: '雨效速度',
rainAudioEnabled: '是否启用雨声',
rainAudioVolume: '雨声音量',
snowEnabled: '是否启用降雪',
snowIntensity: '雪量',
snowSpeed: '降雪速度',
lightningEnabled: '是否启用雷闪',
lightningIntensity: '雷闪强度'
};
const exportActions = {
导出预设: () => {
const preset = {
meta: {
version: 1,
comment: '场景预设导出文件',
exportedAt: new Date().toISOString()
},
comments: Object.fromEntries(
presetKeys.map((key) => [key, presetComments[key]])
),
params: Object.fromEntries(
presetKeys.map((key) => [key, params[key]])
)
};
const blob = new Blob([JSON.stringify(preset, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
link.href = url;
link.download = `scene-preset-${stamp}.json`;
link.click();
URL.revokeObjectURL(url);
}
};
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)); skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度').onChange((value) => oceanScene.setSunElevation(value));
@@ -46,8 +129,8 @@ function setupControls(oceanScene) {
skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射').onChange((value) => oceanScene.setRayleigh(value)); skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射').onChange((value) => oceanScene.setRayleigh(value));
const bloomFolder = gui.addFolder('泛光'); const bloomFolder = gui.addFolder('泛光');
bloomFolder.add(params, 'bloomStrength', 0, 3, 0.01).name('强度').onChange((value) => oceanScene.setBloomStrength(value)); bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度').onChange((value) => oceanScene.setBloomStrength(value));
bloomFolder.add(params, 'bloomRadius', 0, 1, 0.01).name('扩散').onChange((value) => oceanScene.setBloomRadius(value)); bloomFolder.add(params, 'bloomRadius', 0, 3, 0.01).name('扩散').onChange((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)); cloudFolder.add(params, 'cloudCoverage', 0, 1, 0.01).name('覆盖度').onChange((value) => oceanScene.setCloudCoverage(value));
@@ -75,8 +158,7 @@ function setupControls(oceanScene) {
snowFolder.add(params, 'snowIntensity', 0, 1.5, 0.01).name('雪量').onChange((value) => oceanScene.setSnowIntensity(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)); snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度').onChange((value) => oceanScene.setSnowSpeed(value));
skyFolder.open(); gui.close();
rainFolder.open();
} }
main().catch(console.error); main().catch(console.error);