效果一般的雾
This commit is contained in:
15
index.html
15
index.html
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -254,6 +318,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();
|
||||
}
|
||||
|
||||
@@ -416,6 +603,20 @@ export class OceanScene {
|
||||
return 0xd4e8f4;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -512,6 +728,59 @@ export class OceanScene {
|
||||
layer.mesh.visible = opacity > 0.015;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user