黑夜场景未完成
This commit is contained in:
@@ -1 +1 @@
|
|||||||
codex resume 019d2462-a1f1-7a72-947b-70470e482854
|
codex resume 019d284e-bc41-78f0-a961-32ce95bfa96a
|
||||||
|
|||||||
@@ -23,13 +23,18 @@ export class OceanScene {
|
|||||||
this.controls = null;
|
this.controls = null;
|
||||||
this.water = null;
|
this.water = null;
|
||||||
this.sky = null;
|
this.sky = null;
|
||||||
|
this.starField = null;
|
||||||
|
this.moonSprite = null;
|
||||||
|
this.galaxyBand = null;
|
||||||
this.sun = new THREE.Vector3();
|
this.sun = new THREE.Vector3();
|
||||||
this.terrain = null;
|
this.terrain = null;
|
||||||
this.vegetation = null;
|
this.vegetation = null;
|
||||||
this.vegetationSystem = null;
|
this.vegetationSystem = null;
|
||||||
this.pmremGenerator = null;
|
this.pmremGenerator = null;
|
||||||
this.renderTarget = null;
|
this.renderTarget = null;
|
||||||
|
this.ambientLight = null;
|
||||||
this.sunLight = null;
|
this.sunLight = null;
|
||||||
|
this.moonLight = null;
|
||||||
this.vegetationFillLight = null;
|
this.vegetationFillLight = null;
|
||||||
this.lightningLight = null;
|
this.lightningLight = null;
|
||||||
this.lightningCloudGlow = null;
|
this.lightningCloudGlow = null;
|
||||||
@@ -74,6 +79,8 @@ export class OceanScene {
|
|||||||
this.initPostProcessing();
|
this.initPostProcessing();
|
||||||
this.initAudio();
|
this.initAudio();
|
||||||
await this.initSky();
|
await this.initSky();
|
||||||
|
this.initStars();
|
||||||
|
this.initNightSky();
|
||||||
this.initClouds();
|
this.initClouds();
|
||||||
this.initFog();
|
this.initFog();
|
||||||
await this.initWater();
|
await this.initWater();
|
||||||
@@ -136,8 +143,8 @@ export class OceanScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initLighting() {
|
initLighting() {
|
||||||
const ambientLight = new THREE.AmbientLight(0x555555);
|
this.ambientLight = new THREE.AmbientLight(0x8ea0b7, 0.58);
|
||||||
this.scene.add(ambientLight);
|
this.scene.add(this.ambientLight);
|
||||||
|
|
||||||
this.sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
this.sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
this.sunLight.castShadow = true;
|
this.sunLight.castShadow = true;
|
||||||
@@ -151,6 +158,10 @@ export class OceanScene {
|
|||||||
this.sunLight.shadow.camera.bottom = -100;
|
this.sunLight.shadow.camera.bottom = -100;
|
||||||
this.scene.add(this.sunLight);
|
this.scene.add(this.sunLight);
|
||||||
|
|
||||||
|
this.moonLight = new THREE.DirectionalLight(0xa9c7ff, 0);
|
||||||
|
this.moonLight.castShadow = false;
|
||||||
|
this.scene.add(this.moonLight);
|
||||||
|
|
||||||
this.vegetationFillLight = new THREE.DirectionalLight(0xffb06a, 0.95);
|
this.vegetationFillLight = new THREE.DirectionalLight(0xffb06a, 0.95);
|
||||||
this.vegetationFillLight.castShadow = false;
|
this.vegetationFillLight.castShadow = false;
|
||||||
this.scene.add(this.vegetationFillLight);
|
this.scene.add(this.vegetationFillLight);
|
||||||
@@ -532,6 +543,193 @@ export class OceanScene {
|
|||||||
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
|
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initStars() {
|
||||||
|
const starCount = 8000;
|
||||||
|
const positions = new Float32Array(starCount * 3);
|
||||||
|
const colors = new Float32Array(starCount * 3);
|
||||||
|
const sizes = new Float32Array(starCount);
|
||||||
|
const color = new THREE.Color();
|
||||||
|
|
||||||
|
for (let i = 0; i < starCount; i++) {
|
||||||
|
const radius = THREE.MathUtils.randFloat(2200, 4200);
|
||||||
|
const theta = Math.random() * Math.PI * 2.0;
|
||||||
|
const phi = THREE.MathUtils.randFloat(0.015, Math.PI * 0.49);
|
||||||
|
const sinPhi = Math.sin(phi);
|
||||||
|
const x = radius * sinPhi * Math.cos(theta);
|
||||||
|
const y = radius * Math.cos(phi);
|
||||||
|
const z = radius * sinPhi * Math.sin(theta);
|
||||||
|
|
||||||
|
positions[i * 3] = x;
|
||||||
|
positions[i * 3 + 1] = y;
|
||||||
|
positions[i * 3 + 2] = z;
|
||||||
|
|
||||||
|
color.setHSL(
|
||||||
|
THREE.MathUtils.randFloat(0.52, 0.64),
|
||||||
|
THREE.MathUtils.randFloat(0.15, 0.45),
|
||||||
|
THREE.MathUtils.randFloat(0.72, 0.96)
|
||||||
|
);
|
||||||
|
colors[i * 3] = color.r;
|
||||||
|
colors[i * 3 + 1] = color.g;
|
||||||
|
colors[i * 3 + 2] = color.b;
|
||||||
|
sizes[i] = Math.pow(Math.random(), 1.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
|
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||||
|
geometry.setAttribute('sizeNoise', new THREE.BufferAttribute(sizes, 1));
|
||||||
|
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
time: { value: 0 },
|
||||||
|
intensity: { value: this.params.starIntensity }
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
attribute float sizeNoise;
|
||||||
|
varying vec3 vColor;
|
||||||
|
varying float vPulse;
|
||||||
|
uniform float time;
|
||||||
|
uniform float intensity;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vColor = color;
|
||||||
|
vPulse = fract(sizeNoise * 17.0 + time * (0.015 + sizeNoise * 0.035));
|
||||||
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
||||||
|
gl_Position = projectionMatrix * mvPosition;
|
||||||
|
float starSize = 2.8 + sizeNoise * 6.4;
|
||||||
|
float projectedSize = starSize * (6500.0 / -mvPosition.z) * (0.65 + intensity * 0.55);
|
||||||
|
gl_PointSize = max(1.6, projectedSize);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
varying vec3 vColor;
|
||||||
|
varying float vPulse;
|
||||||
|
uniform float intensity;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 p = gl_PointCoord - vec2(0.5);
|
||||||
|
float d = length(p);
|
||||||
|
float core = smoothstep(0.26, 0.0, d);
|
||||||
|
float glow = smoothstep(0.58, 0.06, d);
|
||||||
|
float halo = smoothstep(0.82, 0.16, d);
|
||||||
|
float twinkle = 0.8 + 0.2 * sin(vPulse * 6.2831);
|
||||||
|
float alpha = (core * 1.2 + glow * 0.65 + halo * 0.18) * twinkle * intensity;
|
||||||
|
if (alpha <= 0.001) discard;
|
||||||
|
gl_FragColor = vec4(vColor, alpha);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
transparent: true,
|
||||||
|
depthTest: false,
|
||||||
|
depthWrite: false,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
vertexColors: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.starField = new THREE.Points(geometry, material);
|
||||||
|
this.starField.frustumCulled = false;
|
||||||
|
this.scene.add(this.starField);
|
||||||
|
this.updateStars();
|
||||||
|
}
|
||||||
|
|
||||||
|
initNightSky() {
|
||||||
|
const moonTexture = this.createMoonTexture();
|
||||||
|
const moonMaterial = new THREE.SpriteMaterial({
|
||||||
|
map: moonTexture,
|
||||||
|
color: 0xe9f0ff,
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false,
|
||||||
|
depthTest: false,
|
||||||
|
blending: THREE.AdditiveBlending
|
||||||
|
});
|
||||||
|
this.moonSprite = new THREE.Sprite(moonMaterial);
|
||||||
|
this.moonSprite.scale.setScalar(980);
|
||||||
|
this.scene.add(this.moonSprite);
|
||||||
|
|
||||||
|
const galaxyTexture = this.createGalaxyTexture();
|
||||||
|
const galaxyMaterial = new THREE.SpriteMaterial({
|
||||||
|
map: galaxyTexture,
|
||||||
|
color: 0xa7bedf,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0,
|
||||||
|
depthWrite: false,
|
||||||
|
depthTest: false,
|
||||||
|
blending: THREE.AdditiveBlending
|
||||||
|
});
|
||||||
|
this.galaxyBand = new THREE.Sprite(galaxyMaterial);
|
||||||
|
this.galaxyBand.scale.set(2600, 760, 1);
|
||||||
|
this.scene.add(this.galaxyBand);
|
||||||
|
}
|
||||||
|
|
||||||
|
createMoonTexture() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 256;
|
||||||
|
canvas.height = 256;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const glow = ctx.createRadialGradient(128, 128, 0, 128, 128, 128);
|
||||||
|
glow.addColorStop(0, 'rgba(255,255,255,0.98)');
|
||||||
|
glow.addColorStop(0.2, 'rgba(244,248,255,0.98)');
|
||||||
|
glow.addColorStop(0.34, 'rgba(228,238,255,0.96)');
|
||||||
|
glow.addColorStop(0.58, 'rgba(175,205,255,0.42)');
|
||||||
|
glow.addColorStop(0.82, 'rgba(126,164,235,0.12)');
|
||||||
|
glow.addColorStop(1, 'rgba(120,150,220,0)');
|
||||||
|
ctx.fillStyle = glow;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
ctx.globalCompositeOperation = 'multiply';
|
||||||
|
ctx.fillStyle = 'rgba(150,170,210,0.18)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(152, 104, 34, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(98, 152, 20, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
createGalaxyTexture() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 1024;
|
||||||
|
canvas.height = 256;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const base = ctx.createLinearGradient(0, canvas.height * 0.5, canvas.width, canvas.height * 0.5);
|
||||||
|
base.addColorStop(0, 'rgba(0,0,0,0)');
|
||||||
|
base.addColorStop(0.16, 'rgba(110,130,170,0.04)');
|
||||||
|
base.addColorStop(0.35, 'rgba(165,180,215,0.1)');
|
||||||
|
base.addColorStop(0.5, 'rgba(235,235,255,0.16)');
|
||||||
|
base.addColorStop(0.68, 'rgba(165,180,215,0.1)');
|
||||||
|
base.addColorStop(0.84, 'rgba(110,130,170,0.04)');
|
||||||
|
base.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
ctx.fillStyle = base;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
for (let i = 0; i < 1800; i++) {
|
||||||
|
const x = Math.random() * canvas.width;
|
||||||
|
const yCenter = canvas.height * 0.52 + Math.sin(x * 0.008) * 22.0;
|
||||||
|
const y = yCenter + (Math.random() - 0.5) * 120;
|
||||||
|
const alpha = Math.random() * Math.random() * 0.7;
|
||||||
|
const size = Math.random() < 0.06 ? 2.2 : 1.0;
|
||||||
|
ctx.fillStyle = `rgba(255,255,255,${alpha})`;
|
||||||
|
ctx.fillRect(x, y, size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blur = ctx.createRadialGradient(canvas.width * 0.5, canvas.height * 0.5, 10, canvas.width * 0.5, canvas.height * 0.5, canvas.width * 0.5);
|
||||||
|
blur.addColorStop(0, 'rgba(210,220,255,0.09)');
|
||||||
|
blur.addColorStop(0.5, 'rgba(150,170,220,0.04)');
|
||||||
|
blur.addColorStop(1, 'rgba(0,0,0,0)');
|
||||||
|
ctx.fillStyle = blur;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
initClouds() {
|
initClouds() {
|
||||||
this.cloudGroup = new THREE.Group();
|
this.cloudGroup = new THREE.Group();
|
||||||
this.cloudGroup.position.y = 40;
|
this.cloudGroup.position.y = 40;
|
||||||
@@ -1043,6 +1241,22 @@ export class OceanScene {
|
|||||||
this.water.rotation.x = -Math.PI / 2;
|
this.water.rotation.x = -Math.PI / 2;
|
||||||
this.water.position.y = -0.15;
|
this.water.position.y = -0.15;
|
||||||
this.setWaterColor(this.params.waterColor);
|
this.setWaterColor(this.params.waterColor);
|
||||||
|
|
||||||
|
const baseWaterOnBeforeRender = this.water.onBeforeRender.bind(this.water);
|
||||||
|
this.water.onBeforeRender = (...args) => {
|
||||||
|
const starsWereVisible = this.starField?.visible ?? false;
|
||||||
|
if (this.starField) {
|
||||||
|
this.starField.visible = false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
baseWaterOnBeforeRender(...args);
|
||||||
|
} finally {
|
||||||
|
if (this.starField) {
|
||||||
|
this.starField.visible = starsWereVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.scene.add(this.water);
|
this.scene.add(this.water);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,6 +1349,25 @@ export class OceanScene {
|
|||||||
this.sun.y * sunDistance,
|
this.sun.y * sunDistance,
|
||||||
this.sun.z * sunDistance
|
this.sun.z * sunDistance
|
||||||
);
|
);
|
||||||
|
const dayMix = THREE.MathUtils.clamp((this.sun.y + 0.06) / 0.52, 0, 1);
|
||||||
|
this.sunLight.intensity = THREE.MathUtils.lerp(0.0, 1.5, dayMix);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nightMix = THREE.MathUtils.clamp((-this.sun.y + 0.02) / 0.72, 0, 1);
|
||||||
|
|
||||||
|
if (this.ambientLight) {
|
||||||
|
this.ambientLight.color.set(0x8ea0b7).lerp(new THREE.Color(0x425a77), nightMix);
|
||||||
|
this.ambientLight.intensity = THREE.MathUtils.lerp(0.66, 1.0, nightMix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.moonLight) {
|
||||||
|
const moonDistance = 115;
|
||||||
|
this.moonLight.position.set(
|
||||||
|
-this.sun.x * moonDistance,
|
||||||
|
Math.max(34, -this.sun.y * moonDistance * 0.5 + 42),
|
||||||
|
-this.sun.z * moonDistance
|
||||||
|
);
|
||||||
|
this.moonLight.intensity = 1.15 * nightMix;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vegetationFillLight) {
|
if (this.vegetationFillLight) {
|
||||||
@@ -1144,7 +1377,8 @@ export class OceanScene {
|
|||||||
Math.max(18, this.sun.y * fillDistance * 0.28 + 24),
|
Math.max(18, this.sun.y * fillDistance * 0.28 + 24),
|
||||||
-this.sun.z * fillDistance * 0.35 + 28
|
-this.sun.z * fillDistance * 0.35 + 28
|
||||||
);
|
);
|
||||||
this.vegetationFillLight.intensity = THREE.MathUtils.lerp(0.7, 1.15, THREE.MathUtils.clamp((this.sun.y + 0.2) / 0.9, 0, 1));
|
const fillDayMix = THREE.MathUtils.clamp((this.sun.y + 0.2) / 0.9, 0, 1);
|
||||||
|
this.vegetationFillLight.intensity = THREE.MathUtils.lerp(0.5, 1.15, fillDayMix) + nightMix * 0.16;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.renderTarget) {
|
if (this.renderTarget) {
|
||||||
@@ -1159,13 +1393,16 @@ export class OceanScene {
|
|||||||
|
|
||||||
this.updateFog();
|
this.updateFog();
|
||||||
this.updateClouds();
|
this.updateClouds();
|
||||||
|
this.updateStars();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFogColor() {
|
getFogColor() {
|
||||||
const elevation = this.params.elevation;
|
const elevation = this.params.elevation;
|
||||||
|
|
||||||
if (elevation < 0) {
|
if (elevation < -4) {
|
||||||
return 0x1a2a3a;
|
return 0x121c2c;
|
||||||
|
} else if (elevation < 0) {
|
||||||
|
return 0x1d2b40;
|
||||||
} else if (elevation < 10) {
|
} else if (elevation < 10) {
|
||||||
return 0x4a5a6a;
|
return 0x4a5a6a;
|
||||||
} else if (elevation < 20) {
|
} else if (elevation < 20) {
|
||||||
@@ -1182,13 +1419,17 @@ export class OceanScene {
|
|||||||
getAtmosphereColors() {
|
getAtmosphereColors() {
|
||||||
const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
|
const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
|
||||||
const fogColor = new THREE.Color(this.getFogColor());
|
const fogColor = new THREE.Color(this.getFogColor());
|
||||||
|
const nightMix = THREE.MathUtils.clamp((-this.params.elevation + 1.0) / 12.0, 0, 1);
|
||||||
const warmHorizon = new THREE.Color(0xf0c7a3);
|
const warmHorizon = new THREE.Color(0xf0c7a3);
|
||||||
const coolHorizon = new THREE.Color(0xcfe0ee);
|
const coolHorizon = new THREE.Color(0xcfe0ee);
|
||||||
const horizonColor = warmHorizon.clone().lerp(coolHorizon, sunMix);
|
const nightHorizon = new THREE.Color(0x27415f);
|
||||||
|
const horizonColor = warmHorizon.clone().lerp(coolHorizon, sunMix).lerp(nightHorizon, nightMix);
|
||||||
const warmSkyBase = new THREE.Color(0xf6d7b8);
|
const warmSkyBase = new THREE.Color(0xf6d7b8);
|
||||||
const coolSkyBase = new THREE.Color(0xbfd8eb);
|
const coolSkyBase = new THREE.Color(0xbfd8eb);
|
||||||
const skyBaseColor = warmSkyBase.clone().lerp(coolSkyBase, sunMix * 0.92);
|
const nightSkyBase = new THREE.Color(0x08111f);
|
||||||
const skyBlendColor = skyBaseColor.clone().lerp(fogColor, 0.42);
|
const nightSkyBlend = new THREE.Color(0x182940);
|
||||||
|
const skyBaseColor = warmSkyBase.clone().lerp(coolSkyBase, sunMix * 0.92).lerp(nightSkyBase, nightMix);
|
||||||
|
const skyBlendColor = skyBaseColor.clone().lerp(fogColor, 0.42).lerp(nightSkyBlend, nightMix * 0.78);
|
||||||
|
|
||||||
return { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor };
|
return { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor };
|
||||||
}
|
}
|
||||||
@@ -1376,6 +1617,16 @@ export class OceanScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStarEnabled(value) {
|
||||||
|
this.params.starEnabled = value;
|
||||||
|
this.updateStars();
|
||||||
|
}
|
||||||
|
|
||||||
|
setStarIntensity(value) {
|
||||||
|
this.params.starIntensity = value;
|
||||||
|
this.updateStars();
|
||||||
|
}
|
||||||
|
|
||||||
updateRainAudioState() {
|
updateRainAudioState() {
|
||||||
if (this.rainAudioPool.length === 0) return;
|
if (this.rainAudioPool.length === 0) return;
|
||||||
|
|
||||||
@@ -1485,6 +1736,8 @@ export class OceanScene {
|
|||||||
this.setSnowIntensity(mergedParams.snowIntensity);
|
this.setSnowIntensity(mergedParams.snowIntensity);
|
||||||
this.setSnowSpeed(mergedParams.snowSpeed);
|
this.setSnowSpeed(mergedParams.snowSpeed);
|
||||||
this.setSnowEnabled(mergedParams.snowEnabled);
|
this.setSnowEnabled(mergedParams.snowEnabled);
|
||||||
|
this.setStarIntensity(mergedParams.starIntensity);
|
||||||
|
this.setStarEnabled(mergedParams.starEnabled);
|
||||||
this.setLightningIntensity(mergedParams.lightningIntensity);
|
this.setLightningIntensity(mergedParams.lightningIntensity);
|
||||||
this.setLightningEnabled(mergedParams.lightningEnabled);
|
this.setLightningEnabled(mergedParams.lightningEnabled);
|
||||||
this.setRainEnabled(mergedParams.rainEnabled);
|
this.setRainEnabled(mergedParams.rainEnabled);
|
||||||
@@ -1686,6 +1939,46 @@ export class OceanScene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStars() {
|
||||||
|
if (!this.starField) return;
|
||||||
|
|
||||||
|
const nightFactor = THREE.MathUtils.clamp((12.0 - this.params.elevation) / 16.0, 0.0, 1.0);
|
||||||
|
const weatherFade = this.params.rainEnabled ? 0.08 : this.params.snowEnabled ? 0.55 : 1.0;
|
||||||
|
const opacity = (this.params.starEnabled ? this.params.starIntensity : 0.0) * nightFactor * weatherFade;
|
||||||
|
|
||||||
|
this.starField.visible = opacity > 0.001;
|
||||||
|
this.starField.material.uniforms.intensity.value = opacity;
|
||||||
|
|
||||||
|
if (this.moonSprite) {
|
||||||
|
const moonAngle = THREE.MathUtils.degToRad(this.params.azimuth - 42);
|
||||||
|
const moonDistance = 3200;
|
||||||
|
this.moonSprite.position.set(
|
||||||
|
Math.cos(moonAngle) * moonDistance,
|
||||||
|
THREE.MathUtils.lerp(260, 520, nightFactor),
|
||||||
|
Math.sin(moonAngle) * moonDistance
|
||||||
|
);
|
||||||
|
const moonGlow = THREE.MathUtils.clamp((nightFactor - 0.18) / 0.72, 0, 1);
|
||||||
|
this.moonSprite.visible = moonGlow > 0.01;
|
||||||
|
this.moonSprite.material.opacity = THREE.MathUtils.lerp(0.0, 2.4, moonGlow);
|
||||||
|
const moonScale = THREE.MathUtils.lerp(820, 1460, moonGlow);
|
||||||
|
this.moonSprite.scale.setScalar(moonScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.galaxyBand) {
|
||||||
|
const angle = THREE.MathUtils.degToRad(this.params.azimuth + 36);
|
||||||
|
const radius = 3000;
|
||||||
|
this.galaxyBand.position.set(
|
||||||
|
Math.cos(angle) * radius,
|
||||||
|
THREE.MathUtils.lerp(920, 1500, nightFactor),
|
||||||
|
Math.sin(angle) * radius
|
||||||
|
);
|
||||||
|
this.galaxyBand.material.rotation = THREE.MathUtils.degToRad(-24);
|
||||||
|
const galaxyFade = THREE.MathUtils.clamp((opacity - 0.18) / 1.1, 0, 1) * weatherFade;
|
||||||
|
this.galaxyBand.material.opacity = galaxyFade * 0.92;
|
||||||
|
this.galaxyBand.visible = this.galaxyBand.material.opacity > 0.01;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateFog() {
|
updateFog() {
|
||||||
const { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor } = this.getAtmosphereColors();
|
const { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor } = this.getAtmosphereColors();
|
||||||
const fogDensity = THREE.MathUtils.lerp(0.00015, 0.0018, this.params.fogDensity);
|
const fogDensity = THREE.MathUtils.lerp(0.00015, 0.0018, this.params.fogDensity);
|
||||||
@@ -1745,6 +2038,9 @@ export class OceanScene {
|
|||||||
this.skyHazeBand.material.opacity =
|
this.skyHazeBand.material.opacity =
|
||||||
THREE.MathUtils.lerp(0.04, 0.18, this.params.fogDensity) *
|
THREE.MathUtils.lerp(0.04, 0.18, this.params.fogDensity) *
|
||||||
THREE.MathUtils.lerp(0.78, 1.08, this.params.fogRange);
|
THREE.MathUtils.lerp(0.78, 1.08, this.params.fogRange);
|
||||||
|
const nightHaze = THREE.MathUtils.clamp((-this.params.elevation + 1.0) / 12.0, 0, 1);
|
||||||
|
this.skyHazeBand.material.color.lerp(new THREE.Color(0x203754), nightHaze * 0.72);
|
||||||
|
this.skyHazeBand.material.opacity += nightHaze * 0.12;
|
||||||
this.skyHazeBand.position.y = THREE.MathUtils.lerp(340, 560, this.params.fogHeight);
|
this.skyHazeBand.position.y = THREE.MathUtils.lerp(340, 560, this.params.fogHeight);
|
||||||
this.skyHazeBand.scale.set(
|
this.skyHazeBand.scale.set(
|
||||||
THREE.MathUtils.lerp(0.98, 1.12, this.params.fogRange),
|
THREE.MathUtils.lerp(0.98, 1.12, this.params.fogRange),
|
||||||
@@ -1774,6 +2070,10 @@ export class OceanScene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.starField) {
|
||||||
|
this.starField.material.uniforms.time.value = time;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.fogGroup) {
|
if (this.fogGroup) {
|
||||||
this.fogLayers.forEach((layer, index) => {
|
this.fogLayers.forEach((layer, index) => {
|
||||||
layer.texture.offset.x = time * layer.speedX;
|
layer.texture.offset.x = time * layer.speedX;
|
||||||
@@ -1795,6 +2095,7 @@ export class OceanScene {
|
|||||||
if (this.lightningFlash > 0.001) {
|
if (this.lightningFlash > 0.001) {
|
||||||
this.updateClouds();
|
this.updateClouds();
|
||||||
this.updateFog();
|
this.updateFog();
|
||||||
|
this.updateStars();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rainPass) {
|
if (this.rainPass) {
|
||||||
|
|||||||
32
src/main.js
32
src/main.js
@@ -68,6 +68,8 @@ function setupControls(oceanScene) {
|
|||||||
'snowEnabled',
|
'snowEnabled',
|
||||||
'snowIntensity',
|
'snowIntensity',
|
||||||
'snowSpeed',
|
'snowSpeed',
|
||||||
|
'starEnabled',
|
||||||
|
'starIntensity',
|
||||||
'lightningEnabled',
|
'lightningEnabled',
|
||||||
'lightningIntensity'
|
'lightningIntensity'
|
||||||
];
|
];
|
||||||
@@ -96,6 +98,8 @@ function setupControls(oceanScene) {
|
|||||||
snowEnabled: '是否启用降雪',
|
snowEnabled: '是否启用降雪',
|
||||||
snowIntensity: '雪量',
|
snowIntensity: '雪量',
|
||||||
snowSpeed: '降雪速度',
|
snowSpeed: '降雪速度',
|
||||||
|
starEnabled: '是否启用星空',
|
||||||
|
starIntensity: '星空强度',
|
||||||
lightningEnabled: '是否启用雷闪',
|
lightningEnabled: '是否启用雷闪',
|
||||||
lightningIntensity: '雷闪强度'
|
lightningIntensity: '雷闪强度'
|
||||||
};
|
};
|
||||||
@@ -135,12 +139,25 @@ function setupControls(oceanScene) {
|
|||||||
};
|
};
|
||||||
const refreshControllers = () => {
|
const refreshControllers = () => {
|
||||||
controllers.forEach((controller) => controller.updateDisplay());
|
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) => {
|
const bindController = (controller, applyValue) => {
|
||||||
controllers.push(controller);
|
controllers.push(controller);
|
||||||
controller.onChange((value) => {
|
controller.onChange((value) => {
|
||||||
applyValue(value);
|
applyValue(value);
|
||||||
markPresetCustom();
|
markPresetCustom();
|
||||||
|
updateStarControllerState();
|
||||||
});
|
});
|
||||||
return controller;
|
return controller;
|
||||||
};
|
};
|
||||||
@@ -154,11 +171,23 @@ function setupControls(oceanScene) {
|
|||||||
gui.add(exportActions, '导出预设');
|
gui.add(exportActions, '导出预设');
|
||||||
|
|
||||||
const skyFolder = gui.addFolder('天空');
|
const skyFolder = gui.addFolder('天空');
|
||||||
bindController(skyFolder.add(params, 'elevation', 0, 90, 0.1).name('太阳高度'), (value) => oceanScene.setSunElevation(value));
|
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, '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, '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, 'turbidity', 1, 20, 0.1).name('浑浊度'), (value) => oceanScene.setTurbidity(value));
|
||||||
bindController(skyFolder.add(params, 'rayleigh', 0, 4, 0.01).name('瑞利散射'), (value) => oceanScene.setRayleigh(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('泛光');
|
const bloomFolder = gui.addFolder('泛光');
|
||||||
bindController(bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度'), (value) => oceanScene.setBloomStrength(value));
|
bindController(bloomFolder.add(params, 'bloomStrength', 0, 1, 0.01).name('强度'), (value) => oceanScene.setBloomStrength(value));
|
||||||
@@ -194,6 +223,7 @@ function setupControls(oceanScene) {
|
|||||||
bindController(snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度'), (value) => oceanScene.setSnowSpeed(value));
|
bindController(snowFolder.add(params, 'snowSpeed', 0.2, 2.2, 0.01).name('速度'), (value) => oceanScene.setSnowSpeed(value));
|
||||||
|
|
||||||
gui.close();
|
gui.close();
|
||||||
|
updateStarControllerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export const DEFAULT_SCENE_PARAMS = {
|
|||||||
snowEnabled: false,
|
snowEnabled: false,
|
||||||
snowIntensity: 0.65,
|
snowIntensity: 0.65,
|
||||||
snowSpeed: 0.85,
|
snowSpeed: 0.85,
|
||||||
|
starEnabled: true,
|
||||||
|
starIntensity: 0.7,
|
||||||
lightningEnabled: false,
|
lightningEnabled: false,
|
||||||
lightningIntensity: 0.75,
|
lightningIntensity: 0.75,
|
||||||
mieCoefficient: 0.005,
|
mieCoefficient: 0.005,
|
||||||
@@ -55,6 +57,7 @@ export const WEATHER_PRESETS = {
|
|||||||
fogDensity: 0.12,
|
fogDensity: 0.12,
|
||||||
fogHeight: 0.58,
|
fogHeight: 0.58,
|
||||||
fogRange: 0.28,
|
fogRange: 0.28,
|
||||||
|
starIntensity: 0.22,
|
||||||
lightningEnabled: false
|
lightningEnabled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -76,6 +79,32 @@ export const WEATHER_PRESETS = {
|
|||||||
fogDensity: 0.1,
|
fogDensity: 0.1,
|
||||||
fogHeight: 0.2,
|
fogHeight: 0.2,
|
||||||
fogRange: 0.24,
|
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
|
lightningEnabled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,6 +126,7 @@ export const WEATHER_PRESETS = {
|
|||||||
fogDensity: 0.38,
|
fogDensity: 0.38,
|
||||||
fogHeight: 0.36,
|
fogHeight: 0.36,
|
||||||
fogRange: 0.62,
|
fogRange: 0.62,
|
||||||
|
starIntensity: 0.04,
|
||||||
lightningEnabled: false
|
lightningEnabled: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -125,6 +155,7 @@ export const WEATHER_PRESETS = {
|
|||||||
rainSpeed: 1.16,
|
rainSpeed: 1.16,
|
||||||
rainAudioEnabled: true,
|
rainAudioEnabled: true,
|
||||||
rainAudioVolume: 0.38,
|
rainAudioVolume: 0.38,
|
||||||
|
starIntensity: 0.02,
|
||||||
lightningEnabled: true,
|
lightningEnabled: true,
|
||||||
lightningIntensity: 0.68,
|
lightningIntensity: 0.68,
|
||||||
snowEnabled: false
|
snowEnabled: false
|
||||||
@@ -148,6 +179,7 @@ export const WEATHER_PRESETS = {
|
|||||||
fogDensity: 0.56,
|
fogDensity: 0.56,
|
||||||
fogHeight: 0.42,
|
fogHeight: 0.42,
|
||||||
fogRange: 0.74,
|
fogRange: 0.74,
|
||||||
|
starIntensity: 0.16,
|
||||||
rainEnabled: false,
|
rainEnabled: false,
|
||||||
lightningEnabled: false,
|
lightningEnabled: false,
|
||||||
snowEnabled: true,
|
snowEnabled: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user