Compare commits
10 Commits
9b4dfb68b0
...
c0ca5b51c4
| Author | SHA1 | Date | |
|---|---|---|---|
| c0ca5b51c4 | |||
| 4a9410802d | |||
| 36965a7dc3 | |||
| 562d031171 | |||
| 2b0893b913 | |||
| 92e3dd7792 | |||
| 6b44ebf599 | |||
| 77664bce52 | |||
| e7d4267f60 | |||
| f3581443d6 |
315
index.html
315
index.html
@@ -60,142 +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;
|
||||
left: 20px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -207,185 +71,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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
18
public/models/README.md
Normal file
18
public/models/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
将你选中的海上风机资产导出为 GLB 文件,并放到下面这个路径:
|
||||
|
||||
`public/models/offshore-wind-turbine.glb`
|
||||
|
||||
当前代码会优先加载这个文件:
|
||||
|
||||
- 存在该文件:使用真实 GLB 风机资产
|
||||
- 文件不存在或加载失败:自动回退到程序化风机占位模型
|
||||
|
||||
建议优先选择包含完整塔架、机舱、叶片的海上风机模型。
|
||||
|
||||
当前选定来源:
|
||||
|
||||
- Sketchfab: Generic Wind Turbine (V136 125.5h 145d)
|
||||
- 链接: https://sketchfab.com/3d-models/generic-wind-turbine-v136-1255h-145d-90ad27be20c541d1a0e4818d4e501679
|
||||
- 发布时间: 2020-06-11
|
||||
- 许可: CC Attribution
|
||||
- 规模: 约 2.1k triangles
|
||||
BIN
public/models/offshore-wind-turbine.glb
Normal file
BIN
public/models/offshore-wind-turbine.glb
Normal file
Binary file not shown.
@@ -1 +1 @@
|
||||
codex resume 019d2462-a1f1-7a72-947b-70470e482854
|
||||
codex resume 019d284e-bc41-78f0-a961-32ce95bfa96a
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
229
src/main.js
229
src/main.js
@@ -1,10 +1,11 @@
|
||||
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');
|
||||
|
||||
const oceanScene = new OceanScene(container);
|
||||
|
||||
try {
|
||||
await oceanScene.init();
|
||||
|
||||
@@ -30,52 +31,198 @@ 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 controllers = [];
|
||||
const presetState = { 当前天气: 'default' };
|
||||
const presetOptions = Object.fromEntries(
|
||||
Object.entries(WEATHER_PRESETS).map(([key, preset]) => [preset.label, key])
|
||||
);
|
||||
const presetKeys = [
|
||||
'elevation',
|
||||
'azimuth',
|
||||
'exposure',
|
||||
'turbidity',
|
||||
'rayleigh',
|
||||
'bloomStrength',
|
||||
'bloomRadius',
|
||||
'waterColor',
|
||||
'cloudCoverage',
|
||||
'cloudDensity',
|
||||
'cloudElevation',
|
||||
'fogDensity',
|
||||
'fogHeight',
|
||||
'fogRange',
|
||||
'rainEnabled',
|
||||
'rainScreenIntensity',
|
||||
'rainVeilIntensity',
|
||||
'rainDropSize',
|
||||
'rainSpeed',
|
||||
'rainAudioEnabled',
|
||||
'rainAudioVolume',
|
||||
'snowEnabled',
|
||||
'snowIntensity',
|
||||
'snowSpeed',
|
||||
'starEnabled',
|
||||
'starIntensity',
|
||||
'lightningEnabled',
|
||||
'lightningIntensity'
|
||||
];
|
||||
const presetComments = {
|
||||
elevation: '太阳高度角',
|
||||
azimuth: '太阳方位角',
|
||||
exposure: '场景曝光度',
|
||||
turbidity: '天空浑浊度',
|
||||
rayleigh: '瑞利散射强度',
|
||||
bloomStrength: '泛光强度',
|
||||
bloomRadius: '泛光扩散范围',
|
||||
waterColor: '海水颜色',
|
||||
cloudCoverage: '云层覆盖度',
|
||||
cloudDensity: '云层密度',
|
||||
cloudElevation: '云层高度',
|
||||
fogDensity: '雾气浓度',
|
||||
fogHeight: '雾气高度',
|
||||
fogRange: '雾气范围',
|
||||
rainEnabled: '是否启用雨效',
|
||||
rainScreenIntensity: '屏幕雨滴强度',
|
||||
rainVeilIntensity: '雨线强度',
|
||||
rainDropSize: '雨滴尺寸',
|
||||
rainSpeed: '雨效速度',
|
||||
rainAudioEnabled: '是否启用雨声',
|
||||
rainAudioVolume: '雨声音量',
|
||||
snowEnabled: '是否启用降雪',
|
||||
snowIntensity: '雪量',
|
||||
snowSpeed: '降雪速度',
|
||||
starEnabled: '是否启用星空',
|
||||
starIntensity: '星空强度',
|
||||
lightningEnabled: '是否启用雷闪',
|
||||
lightningIntensity: '雷闪强度'
|
||||
};
|
||||
|
||||
const bindCheckbox = (id, setter) => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (!checkbox) return;
|
||||
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]])
|
||||
)
|
||||
};
|
||||
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
setter(e.target.checked);
|
||||
});
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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 markPresetCustom = () => {
|
||||
if (presetState.当前天气 !== 'custom') {
|
||||
presetState.当前天气 = 'custom';
|
||||
presetController.updateDisplay();
|
||||
}
|
||||
};
|
||||
const refreshControllers = () => {
|
||||
controllers.forEach((controller) => controller.updateDisplay());
|
||||
updateStarControllerState();
|
||||
};
|
||||
const setControllerEnabled = (controller, enabled) => {
|
||||
controller.domElement.style.opacity = enabled ? '1' : '0.45';
|
||||
controller.domElement.style.pointerEvents = enabled ? 'auto' : 'none';
|
||||
controller.enable?.();
|
||||
if (!enabled) {
|
||||
controller.disable?.();
|
||||
}
|
||||
controller.domElement.querySelectorAll('input, select, button').forEach((element) => {
|
||||
element.disabled = !enabled;
|
||||
});
|
||||
};
|
||||
const bindController = (controller, applyValue) => {
|
||||
controllers.push(controller);
|
||||
controller.onChange((value) => {
|
||||
applyValue(value);
|
||||
markPresetCustom();
|
||||
updateStarControllerState();
|
||||
});
|
||||
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('天空');
|
||||
bindController(skyFolder.add(params, 'elevation', -12, 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 starEnabledController = bindController(skyFolder.add(params, 'starEnabled').name('启用星空'), (value) => oceanScene.setStarEnabled(value));
|
||||
const starIntensityController = bindController(skyFolder.add(params, 'starIntensity', 0, 1.5, 0.01).name('星空强度'), (value) => oceanScene.setStarIntensity(value));
|
||||
const updateStarControllerState = () => {
|
||||
const canUseStars = params.elevation < -1.0;
|
||||
if (!canUseStars && params.starEnabled) {
|
||||
oceanScene.setStarEnabled(false);
|
||||
}
|
||||
setControllerEnabled(starEnabledController, canUseStars);
|
||||
setControllerEnabled(starIntensityController, canUseStars);
|
||||
starEnabledController.updateDisplay();
|
||||
starIntensityController.updateDisplay();
|
||||
};
|
||||
|
||||
const bloomFolder = gui.addFolder('泛光');
|
||||
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 waterFolder = gui.addFolder('海水');
|
||||
bindController(waterFolder.addColor(params, 'waterColor').name('颜色'), (value) => oceanScene.setWaterColor(value));
|
||||
|
||||
const cloudFolder = gui.addFolder('云层');
|
||||
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('雾气');
|
||||
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('雨效');
|
||||
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('雪效');
|
||||
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();
|
||||
updateStarControllerState();
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
133
src/objects/OffshoreWindTurbineAsset.js
Normal file
133
src/objects/OffshoreWindTurbineAsset.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
import { WindTurbine } from './WindTurbine.js';
|
||||
|
||||
const DEFAULT_MODEL_URL = '/models/offshore-wind-turbine.glb';
|
||||
|
||||
export class OffshoreWindTurbineAsset {
|
||||
constructor({
|
||||
modelUrl = DEFAULT_MODEL_URL,
|
||||
position = new THREE.Vector3(360, 0, -260),
|
||||
yaw = -Math.PI * 0.18,
|
||||
scale = 1,
|
||||
rotorSpeed = 0.34
|
||||
} = {}) {
|
||||
this.modelUrl = modelUrl;
|
||||
this.position = position.clone();
|
||||
this.yaw = yaw;
|
||||
this.scale = scale;
|
||||
this.rotorSpeed = rotorSpeed;
|
||||
|
||||
this.group = new THREE.Group();
|
||||
this.group.position.copy(this.position);
|
||||
this.group.rotation.y = this.yaw;
|
||||
this.group.scale.setScalar(this.scale);
|
||||
|
||||
this.rotors = [];
|
||||
this.mixer = null;
|
||||
this.model = null;
|
||||
this.usingFallback = false;
|
||||
}
|
||||
|
||||
async load() {
|
||||
try {
|
||||
const gltf = await this.loadGltf(this.modelUrl);
|
||||
this.attachModel(gltf);
|
||||
} catch (error) {
|
||||
console.warn(`风机资产加载失败,回退到程序化风机: ${this.modelUrl}`, error);
|
||||
this.attachFallback();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
loadGltf(url) {
|
||||
const loader = new GLTFLoader();
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(url, resolve, undefined, reject);
|
||||
});
|
||||
}
|
||||
|
||||
attachModel(gltf) {
|
||||
const model = gltf.scene;
|
||||
this.fitModelToTargetHeight(model);
|
||||
this.prepareModel(model);
|
||||
this.alignModelToWaterline(model);
|
||||
this.group.add(model);
|
||||
this.model = model;
|
||||
|
||||
if (gltf.animations?.length) {
|
||||
this.mixer = new THREE.AnimationMixer(model);
|
||||
gltf.animations.forEach((clip) => {
|
||||
this.mixer.clipAction(clip).play();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attachFallback() {
|
||||
const fallback = new WindTurbine({
|
||||
position: new THREE.Vector3(0, 0, 0),
|
||||
yaw: 0,
|
||||
rotorSpeed: this.rotorSpeed
|
||||
});
|
||||
this.group.add(fallback.group);
|
||||
this.rotors = fallback.rotors;
|
||||
this.usingFallback = true;
|
||||
}
|
||||
|
||||
prepareModel(model) {
|
||||
model.traverse((child) => {
|
||||
if (!child.isMesh) return;
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
|
||||
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
||||
materials.forEach((material) => {
|
||||
if (!material) return;
|
||||
if ('color' in material) {
|
||||
material.color.set(0xe8edf3);
|
||||
}
|
||||
if ('metalness' in material && material.metalness < 0.05) {
|
||||
material.metalness = 0.18;
|
||||
}
|
||||
if ('roughness' in material && material.roughness > 0.92) {
|
||||
material.roughness = 0.82;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
fitModelToTargetHeight(model, targetHeight = 340) {
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
if (size.y <= 0.0001) return;
|
||||
|
||||
const scaleFactor = targetHeight / size.y;
|
||||
model.scale.multiplyScalar(scaleFactor);
|
||||
}
|
||||
|
||||
alignModelToWaterline(model) {
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
model.position.y -= box.min.y;
|
||||
}
|
||||
|
||||
addToScene(scene) {
|
||||
scene.add(this.group);
|
||||
}
|
||||
|
||||
faceDirection(direction) {
|
||||
const flatDirection = new THREE.Vector3(direction.x, 0, direction.z);
|
||||
if (flatDirection.lengthSq() < 0.0001) return;
|
||||
|
||||
flatDirection.normalize();
|
||||
const yaw = Math.atan2(flatDirection.x, flatDirection.z);
|
||||
this.group.rotation.y = yaw;
|
||||
}
|
||||
|
||||
update(time, delta = 1 / 60) {
|
||||
if (this.mixer) {
|
||||
this.mixer.update(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/objects/WindTurbine.js
Normal file
161
src/objects/WindTurbine.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class WindTurbine {
|
||||
constructor({
|
||||
position = new THREE.Vector3(360, 0, -260),
|
||||
yaw = -Math.PI * 0.18,
|
||||
rotorSpeed = 0.34
|
||||
} = {}) {
|
||||
this.group = new THREE.Group();
|
||||
this.group.position.copy(position);
|
||||
this.group.rotation.y = yaw;
|
||||
|
||||
this.rotors = [];
|
||||
this.rotorSpeed = rotorSpeed;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const towerMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0xe7edf2,
|
||||
metalness: 0.28,
|
||||
roughness: 0.55
|
||||
});
|
||||
const accentMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0xc4d0da,
|
||||
metalness: 0.35,
|
||||
roughness: 0.48
|
||||
});
|
||||
const bladeMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0xf7fafc,
|
||||
metalness: 0.16,
|
||||
roughness: 0.62
|
||||
});
|
||||
const foundationMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0x71808f,
|
||||
metalness: 0.24,
|
||||
roughness: 0.82
|
||||
});
|
||||
const platformMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0xffffff,
|
||||
transparent: true,
|
||||
opacity: 0.16,
|
||||
roughness: 0.95,
|
||||
metalness: 0
|
||||
});
|
||||
const ringMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0x91a1ae,
|
||||
metalness: 0.38,
|
||||
roughness: 0.42
|
||||
});
|
||||
|
||||
const foundation = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(8.2, 10.6, 36, 24),
|
||||
foundationMaterial
|
||||
);
|
||||
foundation.position.y = -17.8;
|
||||
this.group.add(foundation);
|
||||
|
||||
const transition = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(5.2, 6.8, 9, 24),
|
||||
accentMaterial
|
||||
);
|
||||
transition.position.y = 4.2;
|
||||
this.group.add(transition);
|
||||
|
||||
const tower = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(2.9, 4.8, 92, 28),
|
||||
towerMaterial
|
||||
);
|
||||
tower.position.y = 54;
|
||||
this.group.add(tower);
|
||||
|
||||
const nacelle = new THREE.Mesh(
|
||||
new THREE.CapsuleGeometry(3.2, 11, 8, 16),
|
||||
towerMaterial
|
||||
);
|
||||
nacelle.rotation.z = Math.PI / 2;
|
||||
nacelle.position.set(0, 102, 0);
|
||||
this.group.add(nacelle);
|
||||
|
||||
const tailFin = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1.1, 5.5, 2.8),
|
||||
accentMaterial
|
||||
);
|
||||
tailFin.position.set(-8.5, 102.5, 0);
|
||||
this.group.add(tailFin);
|
||||
|
||||
const rotor = this.createRotor(bladeMaterial, accentMaterial, ringMaterial);
|
||||
rotor.position.set(6.8, 102, 0);
|
||||
rotor.userData.rotationSpeed = this.rotorSpeed;
|
||||
this.group.add(rotor);
|
||||
this.rotors.push(rotor);
|
||||
|
||||
const platform = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(7.6, 9.2, 0.5, 32),
|
||||
platformMaterial
|
||||
);
|
||||
platform.position.y = 0.12;
|
||||
this.group.add(platform);
|
||||
|
||||
this.group.traverse((child) => {
|
||||
if (!child.isMesh) return;
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
});
|
||||
}
|
||||
|
||||
createRotor(bladeMaterial, hubMaterial, ringMaterial) {
|
||||
const rotor = new THREE.Group();
|
||||
|
||||
const hub = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(2.2, 20, 20),
|
||||
hubMaterial
|
||||
);
|
||||
rotor.add(hub);
|
||||
|
||||
const bladeShape = new THREE.Shape();
|
||||
bladeShape.moveTo(-0.3, 0);
|
||||
bladeShape.quadraticCurveTo(1.2, 2.6, 0.7, 22);
|
||||
bladeShape.quadraticCurveTo(0.2, 31.5, -0.55, 38.5);
|
||||
bladeShape.quadraticCurveTo(-1.0, 31, -0.9, 21);
|
||||
bladeShape.quadraticCurveTo(-0.82, 6.8, -0.3, 0);
|
||||
|
||||
const bladeGeometry = new THREE.ExtrudeGeometry(bladeShape, {
|
||||
depth: 0.22,
|
||||
bevelEnabled: false,
|
||||
curveSegments: 20,
|
||||
steps: 1
|
||||
});
|
||||
bladeGeometry.translate(0, -2.2, -0.11);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const blade = new THREE.Mesh(bladeGeometry, bladeMaterial);
|
||||
blade.rotation.x = Math.PI / 2;
|
||||
blade.rotation.z = (i / 3) * Math.PI * 2;
|
||||
blade.position.x = 0.55;
|
||||
rotor.add(blade);
|
||||
}
|
||||
|
||||
const ring = new THREE.Mesh(
|
||||
new THREE.TorusGeometry(2.65, 0.16, 10, 40),
|
||||
ringMaterial
|
||||
);
|
||||
ring.rotation.y = Math.PI / 2;
|
||||
rotor.add(ring);
|
||||
|
||||
return rotor;
|
||||
}
|
||||
|
||||
addToScene(scene) {
|
||||
scene.add(this.group);
|
||||
}
|
||||
|
||||
update(time, delta = 1 / 60) {
|
||||
this.rotors.forEach((rotor, index) => {
|
||||
const gust = 1.0 + Math.sin(time * 0.28 + index * 1.7) * 0.08;
|
||||
rotor.rotation.x += rotor.userData.rotationSpeed * gust * delta;
|
||||
});
|
||||
}
|
||||
}
|
||||
190
src/weatherPresets.js
Normal file
190
src/weatherPresets.js
Normal file
@@ -0,0 +1,190 @@
|
||||
export const DEFAULT_SCENE_PARAMS = {
|
||||
elevation: 1.5,
|
||||
azimuth: -180,
|
||||
exposure: 0.16,
|
||||
turbidity: 2.7,
|
||||
rayleigh: 4.0,
|
||||
bloomStrength: 0.16,
|
||||
bloomRadius: 0,
|
||||
bloomThreshold: 0,
|
||||
waterColor: '#18465a',
|
||||
cloudCoverage: 0.26,
|
||||
cloudDensity: 0.38,
|
||||
cloudElevation: 0.66,
|
||||
fogDensity: 0.16,
|
||||
fogHeight: 0.26,
|
||||
fogRange: 0.38,
|
||||
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,
|
||||
starEnabled: true,
|
||||
starIntensity: 0.7,
|
||||
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: 2.5,
|
||||
azimuth: 0,
|
||||
exposure: 0.28,
|
||||
turbidity: 1.0,
|
||||
rayleigh: 2.18,
|
||||
bloomStrength: 0.22,
|
||||
bloomRadius: 0.38,
|
||||
waterColor: '#2d6f74',
|
||||
cloudCoverage: 0.34,
|
||||
cloudDensity: 0.24,
|
||||
cloudElevation: 0.9,
|
||||
fogDensity: 0.12,
|
||||
fogHeight: 0.58,
|
||||
fogRange: 0.28,
|
||||
starIntensity: 0.22,
|
||||
lightningEnabled: false
|
||||
}
|
||||
},
|
||||
noon: {
|
||||
label: '正午',
|
||||
params: {
|
||||
...DEFAULT_SCENE_PARAMS,
|
||||
elevation: 72,
|
||||
azimuth: 180,
|
||||
exposure: 0.27,
|
||||
turbidity: 8.2,
|
||||
rayleigh: 2.1,
|
||||
bloomStrength: 0.14,
|
||||
bloomRadius: 0.06,
|
||||
waterColor: '#2b78a4',
|
||||
cloudCoverage: 0.18,
|
||||
cloudDensity: 0.28,
|
||||
cloudElevation: 0.7,
|
||||
fogDensity: 0.1,
|
||||
fogHeight: 0.2,
|
||||
fogRange: 0.24,
|
||||
starIntensity: 0.0,
|
||||
lightningEnabled: false
|
||||
}
|
||||
},
|
||||
night: {
|
||||
label: '黑夜',
|
||||
params: {
|
||||
...DEFAULT_SCENE_PARAMS,
|
||||
elevation: -8,
|
||||
azimuth: 205,
|
||||
exposure: 0.2,
|
||||
turbidity: 1.8,
|
||||
rayleigh: 0.2,
|
||||
bloomStrength: 0.12,
|
||||
bloomRadius: 0.08,
|
||||
waterColor: '#07131f',
|
||||
cloudCoverage: 0.03,
|
||||
cloudDensity: 0.08,
|
||||
cloudElevation: 0.78,
|
||||
fogDensity: 0.08,
|
||||
fogHeight: 0.22,
|
||||
fogRange: 0.2,
|
||||
rainEnabled: false,
|
||||
snowEnabled: false,
|
||||
starEnabled: true,
|
||||
starIntensity: 0.4,
|
||||
lightningEnabled: false
|
||||
}
|
||||
},
|
||||
overcast: {
|
||||
label: '阴天',
|
||||
params: {
|
||||
...DEFAULT_SCENE_PARAMS,
|
||||
elevation: 24,
|
||||
azimuth: 168,
|
||||
exposure: 0.11,
|
||||
turbidity: 15.8,
|
||||
rayleigh: 1.2,
|
||||
bloomStrength: 0.08,
|
||||
bloomRadius: 0.18,
|
||||
waterColor: '#315565',
|
||||
cloudCoverage: 0.84,
|
||||
cloudDensity: 0.88,
|
||||
cloudElevation: 0.34,
|
||||
fogDensity: 0.38,
|
||||
fogHeight: 0.36,
|
||||
fogRange: 0.62,
|
||||
starIntensity: 0.04,
|
||||
lightningEnabled: false
|
||||
}
|
||||
},
|
||||
rainy: {
|
||||
label: '下雨',
|
||||
params: {
|
||||
...DEFAULT_SCENE_PARAMS,
|
||||
elevation: 45,
|
||||
azimuth: 170,
|
||||
exposure: 0.085,
|
||||
turbidity: 3.5,
|
||||
rayleigh: 0,
|
||||
bloomStrength: 0,
|
||||
bloomRadius: 0,
|
||||
waterColor: '#223d48',
|
||||
cloudCoverage: 0.48,
|
||||
cloudDensity: 0.92,
|
||||
cloudElevation: 0.82,
|
||||
fogDensity: 0.62,
|
||||
fogHeight: 0.34,
|
||||
fogRange: 0.78,
|
||||
rainEnabled: true,
|
||||
rainScreenIntensity: 0.38,
|
||||
rainVeilIntensity: 1.4,
|
||||
rainDropSize: 0.4,
|
||||
rainSpeed: 1.16,
|
||||
rainAudioEnabled: true,
|
||||
rainAudioVolume: 0.38,
|
||||
starIntensity: 0.02,
|
||||
lightningEnabled: true,
|
||||
lightningIntensity: 0.68,
|
||||
snowEnabled: false
|
||||
}
|
||||
},
|
||||
snow: {
|
||||
label: '降雪',
|
||||
params: {
|
||||
...DEFAULT_SCENE_PARAMS,
|
||||
elevation: 20,
|
||||
azimuth: 172,
|
||||
exposure: 0.1,
|
||||
turbidity: 14.5,
|
||||
rayleigh: 1.4,
|
||||
bloomStrength: 0.06,
|
||||
bloomRadius: 0.12,
|
||||
waterColor: '#355160',
|
||||
cloudCoverage: 0.88,
|
||||
cloudDensity: 0.76,
|
||||
cloudElevation: 0.38,
|
||||
fogDensity: 0.56,
|
||||
fogHeight: 0.42,
|
||||
fogRange: 0.74,
|
||||
starIntensity: 0.16,
|
||||
rainEnabled: false,
|
||||
lightningEnabled: false,
|
||||
snowEnabled: true,
|
||||
snowIntensity: 1.18,
|
||||
snowSpeed: 1.28
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user