效果一般的雾

This commit is contained in:
2026-03-26 11:27:00 +08:00
parent 3593a39fc7
commit 33fcc4c74a
3 changed files with 303 additions and 2 deletions

View File

@@ -222,6 +222,21 @@
<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 class="control-group">
<label>雾浓度 (Fog Density)</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>雾高度 (Fog Height)</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>雾范围 (Fog Range)</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 id="stats">FPS: <span id="fps">60</span></div>

View File

@@ -30,6 +30,10 @@ export class OceanScene {
this.cloudGroup = null;
this.cloudMaterials = [];
this.cloudLayers = [];
this.fogGroup = null;
this.fogLayers = [];
this.horizonFog = null;
this.skyHazeBand = null;
this.params = {
elevation: 2,
@@ -43,6 +47,9 @@ export class OceanScene {
cloudCoverage: 0.4,
cloudDensity: 0.5,
cloudElevation: 0.5,
fogDensity: 0.42,
fogHeight: 0.32,
fogRange: 0.55,
mieCoefficient: 0.005,
mieDirectionalG: 0.8
};
@@ -61,6 +68,7 @@ export class OceanScene {
this.initPostProcessing();
await this.initSky();
this.initClouds();
this.initFog();
await this.initWater();
await this.initTerrain();
await this.initVegetation();
@@ -84,7 +92,7 @@ export class OceanScene {
initScene() {
this.scene = new THREE.Scene();
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0008);
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0006);
}
initCamera() {
@@ -225,6 +233,62 @@ export class OceanScene {
this.cloudGroup.add(mesh);
}
initFog() {
const fogTexture = this.createFogTexture();
this.fogGroup = new THREE.Group();
this.fogLayers = [];
const layerConfigs = [
{ width: 4600, height: 2400, y: 8, opacity: 0.26, speedX: 0.00055, speedY: 0.0001, rotation: 0.08 },
{ width: 3900, height: 1900, y: 22, opacity: 0.2, speedX: -0.00032, speedY: 0.00014, rotation: -0.05 },
{ width: 3200, height: 1500, y: 42, opacity: 0.13, speedX: 0.00024, speedY: -0.00008, rotation: 0.12 }
];
layerConfigs.forEach((config) => {
const texture = fogTexture.clone();
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2.4, 1.4);
texture.needsUpdate = true;
const material = new THREE.MeshBasicMaterial({
map: texture,
alphaMap: texture,
color: 0xdbe7ef,
transparent: true,
opacity: config.opacity,
depthWrite: false,
fog: false,
side: THREE.DoubleSide,
blending: THREE.NormalBlending
});
const geometry = new THREE.PlaneGeometry(config.width, config.height, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
mesh.rotation.z = config.rotation;
mesh.position.y = config.y;
this.fogLayers.push({
mesh,
texture,
baseY: config.y,
baseOpacity: config.opacity,
speedX: config.speedX,
speedY: config.speedY
});
this.fogGroup.add(mesh);
});
this.horizonFog = this.createHorizonFog();
this.fogGroup.add(this.horizonFog);
this.skyHazeBand = this.createSkyHazeBand();
this.fogGroup.add(this.skyHazeBand);
this.scene.add(this.fogGroup);
this.updateFog();
}
createCloudTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
@@ -255,6 +319,129 @@ export class OceanScene {
return texture;
}
createFogTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 42; i++) {
const x = 30 + Math.random() * 452;
const y = 40 + Math.random() * 432;
const radius = 34 + Math.random() * 88;
const gradient = context.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(255,255,255,0.82)');
gradient.addColorStop(0.22, 'rgba(255,255,255,0.58)');
gradient.addColorStop(0.58, 'rgba(255,255,255,0.16)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = gradient;
context.fillRect(x - radius, y - radius, radius * 2, radius * 2);
}
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
texture.needsUpdate = true;
return texture;
}
createHorizonFogTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 512;
const context = canvas.getContext('2d');
const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.16, 'rgba(255,255,255,0.08)');
gradient.addColorStop(0.38, 'rgba(255,255,255,0.62)');
gradient.addColorStop(0.6, 'rgba(255,255,255,0.42)');
gradient.addColorStop(0.82, 'rgba(255,255,255,0.06)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.repeat.set(4, 1);
texture.needsUpdate = true;
return texture;
}
createSkyHazeTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 512;
const context = canvas.getContext('2d');
const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.12, 'rgba(255,255,255,0.04)');
gradient.addColorStop(0.34, 'rgba(255,255,255,0.32)');
gradient.addColorStop(0.56, 'rgba(255,255,255,0.78)');
gradient.addColorStop(0.82, 'rgba(255,255,255,0.18)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.repeat.set(3, 1);
texture.needsUpdate = true;
return texture;
}
createHorizonFog() {
const texture = this.createHorizonFogTexture();
const material = new THREE.MeshBasicMaterial({
map: texture,
alphaMap: texture,
color: 0xdde8f2,
transparent: true,
opacity: 0.38,
fog: false,
depthWrite: false,
side: THREE.BackSide
});
const geometry = new THREE.CylinderGeometry(4700, 4700, 900, 72, 1, true);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = 130;
mesh.renderOrder = -1;
mesh.userData.texture = texture;
return mesh;
}
createSkyHazeBand() {
const texture = this.createSkyHazeTexture();
const material = new THREE.MeshBasicMaterial({
map: texture,
alphaMap: texture,
color: 0xdde8f2,
transparent: true,
opacity: 0.32,
fog: false,
depthWrite: false,
side: THREE.BackSide,
blending: THREE.NormalBlending
});
const geometry = new THREE.CylinderGeometry(5200, 5200, 1600, 72, 1, true);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = 420;
mesh.renderOrder = -2;
mesh.userData.texture = texture;
return mesh;
}
async initWater() {
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
@@ -395,7 +582,7 @@ export class OceanScene {
this.scene.environment = this.renderTarget.texture;
this.scene.add(this.sky);
this.scene.fog.color.setHex(this.getFogColor());
this.updateFog();
this.updateClouds();
}
@@ -417,6 +604,20 @@ export class OceanScene {
}
}
getAtmosphereColors() {
const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
const fogColor = new THREE.Color(this.getFogColor());
const warmHorizon = new THREE.Color(0xf0c7a3);
const coolHorizon = new THREE.Color(0xcfe0ee);
const horizonColor = warmHorizon.clone().lerp(coolHorizon, sunMix);
const warmSkyBase = new THREE.Color(0xf6d7b8);
const coolSkyBase = new THREE.Color(0xbfd8eb);
const skyBaseColor = warmSkyBase.clone().lerp(coolSkyBase, sunMix * 0.92);
const skyBlendColor = skyBaseColor.clone().lerp(fogColor, 0.42);
return { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor };
}
initEventListeners() {
window.addEventListener('resize', () => this.onWindowResize());
}
@@ -495,6 +696,21 @@ export class OceanScene {
}
}
setFogDensity(value) {
this.params.fogDensity = value;
this.updateFog();
}
setFogHeight(value) {
this.params.fogHeight = value;
this.updateFog();
}
setFogRange(value) {
this.params.fogRange = value;
this.updateFog();
}
updateClouds() {
if (!this.cloudGroup) return;
@@ -513,6 +729,59 @@ export class OceanScene {
}
}
updateFog() {
const { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor } = this.getAtmosphereColors();
const fogDensity = THREE.MathUtils.lerp(0.00015, 0.0018, this.params.fogDensity);
if (this.scene.fog) {
this.scene.fog.color.copy(fogColor);
this.scene.fog.density = fogDensity * THREE.MathUtils.lerp(0.7, 1.4, this.params.fogRange);
}
if (!this.fogGroup) return;
const fogLayerColor = horizonColor.clone().lerp(fogColor, 0.55);
const heightBase = THREE.MathUtils.lerp(-20, 95, this.params.fogHeight);
const verticalSpread = THREE.MathUtils.lerp(20, 110, this.params.fogHeight);
const rangeOpacity = THREE.MathUtils.lerp(0.55, 1.35, this.params.fogRange);
this.fogLayers.forEach((layer, index) => {
layer.mesh.position.y = heightBase + index * verticalSpread * 0.36;
layer.mesh.scale.setScalar(THREE.MathUtils.lerp(0.82, 1.2, this.params.fogRange));
layer.mesh.material.opacity = layer.baseOpacity * THREE.MathUtils.lerp(0.18, 1.35, this.params.fogDensity) * rangeOpacity;
layer.mesh.material.color.copy(fogLayerColor);
layer.mesh.visible = layer.mesh.material.opacity > 0.01;
});
if (this.horizonFog) {
this.horizonFog.material.color.copy(horizonColor);
this.horizonFog.material.opacity =
THREE.MathUtils.lerp(0.16, 0.5, this.params.fogDensity) *
THREE.MathUtils.lerp(0.7, 1.28, this.params.fogRange);
this.horizonFog.position.y = THREE.MathUtils.lerp(70, 190, this.params.fogHeight);
this.horizonFog.scale.set(
THREE.MathUtils.lerp(0.88, 1.28, this.params.fogRange),
THREE.MathUtils.lerp(0.8, 1.18, this.params.fogHeight),
THREE.MathUtils.lerp(0.88, 1.28, this.params.fogRange)
);
this.horizonFog.visible = this.horizonFog.material.opacity > 0.01;
}
if (this.skyHazeBand) {
this.skyHazeBand.material.color.copy(skyBlendColor);
this.skyHazeBand.material.opacity =
THREE.MathUtils.lerp(0.1, 0.32, this.params.fogDensity) *
THREE.MathUtils.lerp(0.82, 1.18, this.params.fogRange);
this.skyHazeBand.position.y = THREE.MathUtils.lerp(300, 520, this.params.fogHeight);
this.skyHazeBand.scale.set(
THREE.MathUtils.lerp(0.96, 1.16, this.params.fogRange),
THREE.MathUtils.lerp(0.88, 1.14, this.params.fogHeight),
THREE.MathUtils.lerp(0.96, 1.16, this.params.fogRange)
);
this.skyHazeBand.visible = this.skyHazeBand.material.opacity > 0.01;
}
}
animate() {
requestAnimationFrame(() => this.animate());
@@ -530,6 +799,20 @@ export class OceanScene {
});
}
if (this.fogGroup) {
this.fogLayers.forEach((layer, index) => {
layer.texture.offset.x = time * layer.speedX;
layer.texture.offset.y = time * layer.speedY;
layer.mesh.rotation.z += Math.sin(time * 0.05 + index) * 0.00002;
});
if (this.horizonFog?.userData.texture) {
this.horizonFog.userData.texture.offset.x = time * 0.00035;
}
if (this.skyHazeBand?.userData.texture) {
this.skyHazeBand.userData.texture.offset.x = -time * 0.00018;
}
}
if (this.vegetationSystem) {
this.vegetationSystem.update(time);
}

View File

@@ -52,6 +52,9 @@ function setupControls(oceanScene) {
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));
}
main().catch(console.error);