效果一般的雾
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">
|
<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 class="control-value" id="cloud-elevation-value">0.50</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div id="stats">FPS: <span id="fps">60</span></div>
|
<div id="stats">FPS: <span id="fps">60</span></div>
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export class OceanScene {
|
|||||||
this.cloudGroup = null;
|
this.cloudGroup = null;
|
||||||
this.cloudMaterials = [];
|
this.cloudMaterials = [];
|
||||||
this.cloudLayers = [];
|
this.cloudLayers = [];
|
||||||
|
this.fogGroup = null;
|
||||||
|
this.fogLayers = [];
|
||||||
|
this.horizonFog = null;
|
||||||
|
this.skyHazeBand = null;
|
||||||
|
|
||||||
this.params = {
|
this.params = {
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -43,6 +47,9 @@ export class OceanScene {
|
|||||||
cloudCoverage: 0.4,
|
cloudCoverage: 0.4,
|
||||||
cloudDensity: 0.5,
|
cloudDensity: 0.5,
|
||||||
cloudElevation: 0.5,
|
cloudElevation: 0.5,
|
||||||
|
fogDensity: 0.42,
|
||||||
|
fogHeight: 0.32,
|
||||||
|
fogRange: 0.55,
|
||||||
mieCoefficient: 0.005,
|
mieCoefficient: 0.005,
|
||||||
mieDirectionalG: 0.8
|
mieDirectionalG: 0.8
|
||||||
};
|
};
|
||||||
@@ -61,6 +68,7 @@ export class OceanScene {
|
|||||||
this.initPostProcessing();
|
this.initPostProcessing();
|
||||||
await this.initSky();
|
await this.initSky();
|
||||||
this.initClouds();
|
this.initClouds();
|
||||||
|
this.initFog();
|
||||||
await this.initWater();
|
await this.initWater();
|
||||||
await this.initTerrain();
|
await this.initTerrain();
|
||||||
await this.initVegetation();
|
await this.initVegetation();
|
||||||
@@ -84,7 +92,7 @@ export class OceanScene {
|
|||||||
|
|
||||||
initScene() {
|
initScene() {
|
||||||
this.scene = new THREE.Scene();
|
this.scene = new THREE.Scene();
|
||||||
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0008);
|
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0006);
|
||||||
}
|
}
|
||||||
|
|
||||||
initCamera() {
|
initCamera() {
|
||||||
@@ -225,6 +233,62 @@ export class OceanScene {
|
|||||||
this.cloudGroup.add(mesh);
|
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() {
|
createCloudTexture() {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = 256;
|
canvas.width = 256;
|
||||||
@@ -255,6 +319,129 @@ export class OceanScene {
|
|||||||
return texture;
|
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() {
|
async initWater() {
|
||||||
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
|
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
|
||||||
|
|
||||||
@@ -395,7 +582,7 @@ export class OceanScene {
|
|||||||
this.scene.environment = this.renderTarget.texture;
|
this.scene.environment = this.renderTarget.texture;
|
||||||
this.scene.add(this.sky);
|
this.scene.add(this.sky);
|
||||||
|
|
||||||
this.scene.fog.color.setHex(this.getFogColor());
|
this.updateFog();
|
||||||
this.updateClouds();
|
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() {
|
initEventListeners() {
|
||||||
window.addEventListener('resize', () => this.onWindowResize());
|
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() {
|
updateClouds() {
|
||||||
if (!this.cloudGroup) return;
|
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() {
|
animate() {
|
||||||
requestAnimationFrame(() => this.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) {
|
if (this.vegetationSystem) {
|
||||||
this.vegetationSystem.update(time);
|
this.vegetationSystem.update(time);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ function setupControls(oceanScene) {
|
|||||||
bindSlider('cloud-coverage', (value) => value.toFixed(2), (value) => oceanScene.setCloudCoverage(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-density', (value) => value.toFixed(2), (value) => oceanScene.setCloudDensity(value));
|
||||||
bindSlider('cloud-elevation', (value) => value.toFixed(2), (value) => oceanScene.setCloudElevation(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);
|
main().catch(console.error);
|
||||||
|
|||||||
Reference in New Issue
Block a user