重写控制面板
This commit is contained in:
302
index.html
302
index.html
@@ -60,131 +60,6 @@
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#info {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 15px 20px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(10px);
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#info h2 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#info p {
|
||||
margin: 5px 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#sun-controls {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(10px);
|
||||
min-width: 280px;
|
||||
max-height: calc(100vh - 40px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#sun-controls h3 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.control-section-title {
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.control-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.control-group.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
font-size: 11px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.control-group input[type="range"] {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: rgba(255,255,255,0.2);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.control-group input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #4a9eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255,255,255,0.06);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-toggle label {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.control-toggle input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #5da9ff;
|
||||
}
|
||||
|
||||
.control-value {
|
||||
text-align: right;
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
#stats {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
@@ -207,183 +82,6 @@
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<div id="info">
|
||||
<h2>🌊 写实无尽海洋</h2>
|
||||
<p>🖱️ 左键拖动旋转视角</p>
|
||||
<p>🖱️ 右键拖动平移</p>
|
||||
<p>🖱️ 滚轮缩放</p>
|
||||
<p>☀️ 使用右上角控制太阳</p>
|
||||
</div>
|
||||
|
||||
<div id="sun-controls">
|
||||
<h3>☀️ 场景控制</h3>
|
||||
|
||||
<div class="control-section">
|
||||
<div class="control-section-title">太阳 & 光照</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group">
|
||||
<label>太阳高度</label>
|
||||
<input type="range" id="sun-elevation" min="0" max="90" value="2" step="0.1">
|
||||
<div class="control-value" id="sun-elevation-value">2.0°</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>太阳方位</label>
|
||||
<input type="range" id="sun-azimuth" min="-180" max="180" value="180" step="0.1">
|
||||
<div class="control-value" id="sun-azimuth-value">180.0°</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>曝光度</label>
|
||||
<input type="range" id="exposure" min="0" max="1" value="0.1" step="0.01">
|
||||
<div class="control-value" id="exposure-value">0.10</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>浑浊度</label>
|
||||
<input type="range" id="turbidity" min="1" max="20" value="10" step="0.1">
|
||||
<div class="control-value" id="turbidity-value">10.0</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>瑞利散射</label>
|
||||
<input type="range" id="rayleigh" min="0" max="4" value="2" step="0.01">
|
||||
<div class="control-value" id="rayleigh-value">2.00</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<div class="control-section-title">Bloom 效果</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group">
|
||||
<label>强度</label>
|
||||
<input type="range" id="bloom-strength" min="0" max="3" value="0.1" step="0.01">
|
||||
<div class="control-value" id="bloom-strength-value">0.10</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>扩散</label>
|
||||
<input type="range" id="bloom-radius" min="0" max="1" value="0" step="0.01">
|
||||
<div class="control-value" id="bloom-radius-value">0.00</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<div class="control-section-title">云层</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group">
|
||||
<label>覆盖度</label>
|
||||
<input type="range" id="cloud-coverage" min="0" max="1" value="0.4" step="0.01">
|
||||
<div class="control-value" id="cloud-coverage-value">0.40</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>密度</label>
|
||||
<input type="range" id="cloud-density" min="0" max="1" value="0.5" step="0.01">
|
||||
<div class="control-value" id="cloud-density-value">0.50</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>高度</label>
|
||||
<input type="range" id="cloud-elevation" min="0" max="1" value="0.5" step="0.01">
|
||||
<div class="control-value" id="cloud-elevation-value">0.50</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<div class="control-section-title">雾气</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group">
|
||||
<label>浓度</label>
|
||||
<input type="range" id="fog-density" min="0" max="1" value="0.42" step="0.01">
|
||||
<div class="control-value" id="fog-density-value">0.42</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>高度</label>
|
||||
<input type="range" id="fog-height" min="0" max="1" value="0.32" step="0.01">
|
||||
<div class="control-value" id="fog-height-value">0.32</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>范围</label>
|
||||
<input type="range" id="fog-range" min="0" max="1" value="0.55" step="0.01">
|
||||
<div class="control-value" id="fog-range-value">0.55</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<div class="control-section-title">雨效</div>
|
||||
<div class="control-toggle">
|
||||
<label for="rain-enabled">启用镜头雨幕</label>
|
||||
<input type="checkbox" id="rain-enabled">
|
||||
</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group">
|
||||
<label>屏幕雨滴</label>
|
||||
<input type="range" id="rain-screen-intensity" min="0" max="1.5" value="0.41" step="0.01">
|
||||
<div class="control-value" id="rain-screen-intensity-value">0.41</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>雨线强度</label>
|
||||
<input type="range" id="rain-veil-intensity" min="0" max="1.5" value="1.15" step="0.01">
|
||||
<div class="control-value" id="rain-veil-intensity-value">1.15</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>雨滴尺寸</label>
|
||||
<input type="range" id="rain-drop-size" min="0.4" max="1.8" value="1.00" step="0.01">
|
||||
<div class="control-value" id="rain-drop-size-value">1.00</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>速度</label>
|
||||
<input type="range" id="rain-speed" min="0.2" max="2.5" value="1.00" step="0.01">
|
||||
<div class="control-value" id="rain-speed-value">1.00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-toggle" style="margin-top: 8px;">
|
||||
<label for="rain-audio-enabled">启用雨声</label>
|
||||
<input type="checkbox" id="rain-audio-enabled" checked>
|
||||
</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group full-width">
|
||||
<label>雨声音量</label>
|
||||
<input type="range" id="rain-audio-volume" min="0" max="1" value="0.35" step="0.01">
|
||||
<div class="control-value" id="rain-audio-volume-value">0.35</div>
|
||||
</div>
|
||||
</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">
|
||||
<label for="lightning-enabled">启用雷闪</label>
|
||||
<input type="checkbox" id="lightning-enabled" checked>
|
||||
</div>
|
||||
<div class="control-grid">
|
||||
<div class="control-group full-width">
|
||||
<label>雷闪强度</label>
|
||||
<input type="range" id="lightning-intensity" min="0" max="1.5" value="0.75" step="0.01">
|
||||
<div class="control-value" id="lightning-intensity-value">0.75</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="stats">FPS: <span id="fps">60</span></div>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
@@ -1355,7 +1355,7 @@ export class OceanScene {
|
||||
1
|
||||
);
|
||||
const delay = THREE.MathUtils.lerp(0.65, 2.4, distanceNorm) + Math.random() * 0.45;
|
||||
const volume = this.params.lightningIntensity * THREE.MathUtils.lerp(0.9, 0.42, distanceNorm);
|
||||
const volume = this.params.lightningIntensity * THREE.MathUtils.lerp(1.0, 0.58, distanceNorm) * 1.12;
|
||||
this.scheduledThunder.push({
|
||||
playAt: this.lightningBurstEnd + delay,
|
||||
volume,
|
||||
|
||||
85
src/main.js
85
src/main.js
@@ -1,3 +1,4 @@
|
||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||||
import { OceanScene } from './OceanScene.js';
|
||||
|
||||
async function main() {
|
||||
@@ -30,52 +31,52 @@ async function main() {
|
||||
}
|
||||
|
||||
function setupControls(oceanScene) {
|
||||
const bindSlider = (id, formatter, setter) => {
|
||||
const slider = document.getElementById(id);
|
||||
const valueLabel = document.getElementById(`${id}-value`);
|
||||
if (!slider || !valueLabel) return;
|
||||
const gui = new GUI({ title: '场景控制' });
|
||||
gui.domElement.style.marginTop = '12px';
|
||||
gui.domElement.style.marginRight = '12px';
|
||||
gui.domElement.style.zIndex = '120';
|
||||
|
||||
slider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
setter(value);
|
||||
valueLabel.textContent = formatter(value);
|
||||
});
|
||||
};
|
||||
const params = oceanScene.params;
|
||||
|
||||
const bindCheckbox = (id, setter) => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (!checkbox) return;
|
||||
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));
|
||||
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
setter(e.target.checked);
|
||||
});
|
||||
};
|
||||
const bloomFolder = gui.addFolder('泛光');
|
||||
bloomFolder.add(params, 'bloomStrength', 0, 3, 0.01).name('强度').onChange((value) => oceanScene.setBloomStrength(value));
|
||||
bloomFolder.add(params, 'bloomRadius', 0, 1, 0.01).name('扩散').onChange((value) => oceanScene.setBloomRadius(value));
|
||||
|
||||
bindSlider('sun-elevation', (value) => `${value.toFixed(1)}°`, (value) => oceanScene.setSunElevation(value));
|
||||
bindSlider('sun-azimuth', (value) => `${value.toFixed(1)}°`, (value) => oceanScene.setSunAzimuth(value));
|
||||
bindSlider('exposure', (value) => value.toFixed(2), (value) => oceanScene.setExposure(value));
|
||||
bindSlider('turbidity', (value) => value.toFixed(1), (value) => oceanScene.setTurbidity(value));
|
||||
bindSlider('rayleigh', (value) => value.toFixed(2), (value) => oceanScene.setRayleigh(value));
|
||||
bindSlider('bloom-strength', (value) => value.toFixed(2), (value) => oceanScene.setBloomStrength(value));
|
||||
bindSlider('bloom-radius', (value) => value.toFixed(2), (value) => oceanScene.setBloomRadius(value));
|
||||
bindSlider('cloud-coverage', (value) => value.toFixed(2), (value) => oceanScene.setCloudCoverage(value));
|
||||
bindSlider('cloud-density', (value) => value.toFixed(2), (value) => oceanScene.setCloudDensity(value));
|
||||
bindSlider('cloud-elevation', (value) => value.toFixed(2), (value) => oceanScene.setCloudElevation(value));
|
||||
bindSlider('fog-density', (value) => value.toFixed(2), (value) => oceanScene.setFogDensity(value));
|
||||
bindSlider('fog-height', (value) => value.toFixed(2), (value) => oceanScene.setFogHeight(value));
|
||||
bindSlider('fog-range', (value) => value.toFixed(2), (value) => oceanScene.setFogRange(value));
|
||||
bindSlider('rain-screen-intensity', (value) => value.toFixed(2), (value) => oceanScene.setRainScreenIntensity(value));
|
||||
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('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));
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
skyFolder.open();
|
||||
rainFolder.open();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
Reference in New Issue
Block a user