调整云

This commit is contained in:
2026-03-26 17:29:15 +08:00
parent 6b44ebf599
commit 92e3dd7792

View File

@@ -520,6 +520,7 @@ export class OceanScene {
async initSky() { async initSky() {
this.sky = new Sky(); this.sky = new Sky();
this.sky.scale.setScalar(10000); this.sky.scale.setScalar(10000);
this.sky.rotation.y = Math.PI;
this.scene.add(this.sky); this.scene.add(this.sky);
const skyUniforms = this.sky.material.uniforms; const skyUniforms = this.sky.material.uniforms;
@@ -532,24 +533,55 @@ export class OceanScene {
} }
initClouds() { initClouds() {
const cloudTexture = this.createCloudTexture();
this.cloudGroup = new THREE.Group(); this.cloudGroup = new THREE.Group();
this.cloudGroup.position.y = -120; this.cloudGroup.position.y = 40;
this.addCloudDomeLayer(cloudTexture, { this.addCloudPlaneLayer({
radius: 4200, radius: 5200,
y: 120,
opacity: 0.42, opacity: 0.42,
repeatX: 5.5, scale: 1450,
repeatY: 2.4, detailScale: 3.4,
speedX: 0.00045, softness: 0.19,
speedY: 0.00012 edgeFade: 0.2,
shadowStrength: 0.36,
highlightStrength: 0.18,
erosionStrength: 0.2,
ridgeStrength: 0.08,
driftX: 0.0045,
driftY: 0.0012,
rotationZ: 0.06
}); });
this.addCloudDomeLayer(cloudTexture, { this.addCloudPlaneLayer({
radius: 3900, radius: 4300,
y: 250,
opacity: 0.28, opacity: 0.28,
repeatX: 7.5, scale: 980,
repeatY: 3.2, detailScale: 4.1,
speedX: -0.00032, softness: 0.17,
speedY: 0.00018 edgeFade: 0.24,
shadowStrength: 0.42,
highlightStrength: 0.24,
erosionStrength: 0.28,
ridgeStrength: 0.12,
driftX: -0.0032,
driftY: 0.0018,
rotationZ: -0.04
});
this.addCloudPlaneLayer({
radius: 3400,
y: 360,
opacity: 0.18,
scale: 760,
detailScale: 4.8,
softness: 0.16,
edgeFade: 0.28,
shadowStrength: 0.46,
highlightStrength: 0.3,
erosionStrength: 0.34,
ridgeStrength: 0.16,
driftX: 0.0021,
driftY: -0.0014,
rotationZ: 0.1
}); });
this.scene.add(this.cloudGroup); this.scene.add(this.cloudGroup);
@@ -579,44 +611,151 @@ export class OceanScene {
this.scene.add(this.lightningCloudGlow); this.scene.add(this.lightningCloudGlow);
} }
addCloudDomeLayer(texture, config) { addCloudPlaneLayer(config) {
const layerTexture = texture.clone(); const material = new THREE.ShaderMaterial({
layerTexture.wrapS = THREE.RepeatWrapping; uniforms: {
layerTexture.wrapT = THREE.RepeatWrapping; tintColor: { value: new THREE.Color(0xffffff) },
layerTexture.repeat.set(config.repeatX, config.repeatY); layerOpacity: { value: config.opacity },
layerTexture.needsUpdate = true; time: { value: 0 },
cloudCoverage: { value: this.params.cloudCoverage },
cloudDensity: { value: this.params.cloudDensity },
scale: { value: config.scale },
detailScale: { value: config.detailScale },
softness: { value: config.softness },
edgeFade: { value: config.edgeFade },
drift: { value: new THREE.Vector2(config.driftX, config.driftY) },
seed: { value: Math.random() * 100.0 },
lightDir: { value: new THREE.Vector2(1, 0) },
shadowStrength: { value: config.shadowStrength ?? 0.42 },
highlightStrength: { value: config.highlightStrength ?? 0.26 },
erosionStrength: { value: config.erosionStrength ?? 0.24 },
ridgeStrength: { value: config.ridgeStrength ?? 0.12 }
},
vertexShader: `
varying vec2 vPlaneUv;
varying vec3 vWorldPos;
const material = new THREE.MeshBasicMaterial({ void main() {
map: layerTexture, vPlaneUv = uv;
alphaMap: layerTexture, vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
color: 0xffffff, gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 tintColor;
uniform float layerOpacity;
uniform float time;
uniform float cloudCoverage;
uniform float cloudDensity;
uniform float scale;
uniform float detailScale;
uniform float softness;
uniform float edgeFade;
uniform vec2 drift;
uniform float seed;
uniform vec2 lightDir;
uniform float shadowStrength;
uniform float highlightStrength;
uniform float erosionStrength;
uniform float ridgeStrength;
varying vec2 vPlaneUv;
varying vec3 vWorldPos;
float hash(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 78.233);
return fract(p.x * p.y);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
u.y
);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 5; i++) {
value += amplitude * noise(p);
p = p * 2.03 + vec2(11.7, 7.3);
amplitude *= 0.5;
}
return value;
}
void main() {
vec2 flow = vWorldPos.xz / scale + drift * time + vec2(seed);
float base = fbm(flow);
float detail = fbm(flow * detailScale + vec2(0.0, time * 0.01));
float wisps = fbm(flow * (detailScale * 0.55) - vec2(time * 0.008, 0.0));
float billow = fbm(flow * 0.62 + vec2(seed * 0.37, -seed * 0.21));
float erosion = fbm(flow * (detailScale * 1.8) + vec2(seed * 1.3, -seed * 0.9));
float ridge = 1.0 - abs(fbm(flow * (detailScale * 0.85) - vec2(seed * 0.6, seed * 0.45)) * 2.0 - 1.0);
float shape = base * 0.5 + billow * 0.24 + detail * 0.14 + wisps * 0.12;
shape -= erosion * erosionStrength;
shape += ridge * ridgeStrength;
float densityBoost = mix(0.78, 1.28, clamp(cloudDensity, 0.0, 1.0));
float coverageThreshold = mix(0.84, 0.34, clamp(cloudCoverage, 0.0, 1.0));
float alpha = smoothstep(
coverageThreshold + softness,
coverageThreshold - softness,
shape * densityBoost
);
float core = smoothstep(
coverageThreshold + softness * 0.4,
coverageThreshold - softness * 1.4,
shape * densityBoost
);
vec2 eps = vec2(0.03, 0.0);
float sampleA = fbm((flow + lightDir * eps.x) * 0.62 + vec2(seed * 0.37, -seed * 0.21));
float sampleB = fbm((flow - lightDir * eps.x) * 0.62 + vec2(seed * 0.37, -seed * 0.21));
float lightEdge = clamp((sampleA - sampleB) * 3.2 + 0.5, 0.0, 1.0);
float underside = 1.0 - smoothstep(0.32, 0.88, shape);
float radial = distance(vPlaneUv, vec2(0.5)) * 2.0;
float edgeMask = 1.0 - smoothstep(1.0 - edgeFade, 1.0, radial);
alpha *= edgeMask * layerOpacity;
if (alpha <= 0.001) discard;
vec3 shadowColor = tintColor * (1.0 - shadowStrength);
vec3 litColor = mix(shadowColor, tintColor, core);
litColor += tintColor * lightEdge * core * highlightStrength;
litColor = mix(litColor, shadowColor, underside * shadowStrength * 0.7);
gl_FragColor = vec4(litColor, alpha);
}
`,
transparent: true, transparent: true,
opacity: config.opacity, side: THREE.DoubleSide,
side: THREE.BackSide,
depthWrite: false, depthWrite: false,
alphaTest: 0.08,
fog: false fog: false
}); });
const geometry = new THREE.SphereGeometry( const geometry = new THREE.CircleGeometry(config.radius, 96);
config.radius,
32,
20,
0,
Math.PI * 2,
0,
Math.PI * 0.48
);
const mesh = new THREE.Mesh(geometry, material); const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.y = Math.random() * Math.PI * 2; mesh.rotation.x = -Math.PI / 2;
this.cloudMaterials.push(material); mesh.rotation.z = config.rotationZ ?? 0;
mesh.position.y = config.y;
this.cloudLayers.push({ this.cloudLayers.push({
mesh, mesh,
texture: layerTexture, material,
speedX: config.speedX, baseOpacity: config.opacity,
speedY: config.speedY, baseY: config.y,
baseOpacity: config.opacity baseScale: config.scale,
baseSoftness: config.softness,
baseShadowStrength: config.shadowStrength ?? 0.42,
baseHighlightStrength: config.highlightStrength ?? 0.26,
baseErosionStrength: config.erosionStrength ?? 0.24,
baseRidgeStrength: config.ridgeStrength ?? 0.12
}); });
this.cloudGroup.add(mesh); this.cloudGroup.add(mesh);
} }
@@ -677,36 +816,6 @@ export class OceanScene {
this.updateFog(); this.updateFog();
} }
createCloudTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 28; i++) {
const x = 28 + Math.random() * 200;
const y = 52 + Math.random() * 152;
const radius = 28 + Math.random() * 44;
const gradient = context.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(255,255,255,0.92)');
gradient.addColorStop(0.28, 'rgba(255,255,255,0.78)');
gradient.addColorStop(0.62, 'rgba(255,255,255,0.22)');
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;
}
createLightningGlowTexture() { createLightningGlowTexture() {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 256; canvas.width = 256;
@@ -1122,7 +1231,7 @@ export class OceanScene {
setCloudElevation(value) { setCloudElevation(value) {
this.params.cloudElevation = value; this.params.cloudElevation = value;
if (this.cloudGroup) { if (this.cloudGroup) {
this.cloudGroup.position.y = THREE.MathUtils.lerp(-380, 260, value); this.cloudGroup.position.y = THREE.MathUtils.lerp(-160, 260, value);
} }
} }
@@ -1484,20 +1593,48 @@ export class OceanScene {
if (!this.cloudGroup) return; if (!this.cloudGroup) return;
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 dawnMix = 1.0 - THREE.MathUtils.smoothstep(this.params.elevation, 8, 34);
const rainMix = this.params.rainEnabled ? THREE.MathUtils.clamp(this.params.rainVeilIntensity / 2.0, 0.35, 1.0) : 0;
const snowMix = this.params.snowEnabled ? THREE.MathUtils.clamp(this.params.snowIntensity / 1.5, 0.35, 1.0) : 0;
const stormMix = Math.max(rainMix, snowMix);
const warmCloud = new THREE.Color(0xdab188); const warmCloud = new THREE.Color(0xdab188);
const dayCloud = new THREE.Color(0xd1dbe6); const dayCloud = new THREE.Color(0xd1dbe6);
const cloudColor = warmCloud.lerp(dayCloud, sunMix); const cloudColor = warmCloud.lerp(dayCloud, sunMix);
const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.32, 0, 1); const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.32, 0, 1);
cloudColor.lerp(new THREE.Color(0xbfcad6), stormMix * 0.45);
cloudColor.lerp(new THREE.Color(0xe9f3ff), lightningMix); cloudColor.lerp(new THREE.Color(0xe9f3ff), lightningMix);
const lightDir = new THREE.Vector2(this.sun.x, this.sun.z).normalize();
for (const layer of this.cloudLayers) { this.cloudLayers.forEach((layer, index) => {
const coverageFactor = 0.15 + this.params.cloudCoverage * 1.15; const coverageFactor = 0.15 + this.params.cloudCoverage * 1.15;
const densityFactor = 0.2 + this.params.cloudDensity * 1.35; const densityFactor = 0.2 + this.params.cloudDensity * 1.35;
const opacity = layer.baseOpacity * coverageFactor * densityFactor; const layerDepth = index / Math.max(this.cloudLayers.length - 1, 1);
layer.mesh.material.opacity = opacity; const weatherOpacityBoost = THREE.MathUtils.lerp(1.0, 1.42 - layerDepth * 0.16, rainMix);
layer.mesh.material.color.copy(cloudColor); const snowOpacityBoost = THREE.MathUtils.lerp(1.0, 1.22 - layerDepth * 0.08, snowMix);
const opacity = layer.baseOpacity * coverageFactor * densityFactor * weatherOpacityBoost * snowOpacityBoost;
const heightDrop = THREE.MathUtils.lerp(0, 120 + index * 55, rainMix);
const snowLift = THREE.MathUtils.lerp(0, 40 + index * 24, snowMix);
const layerScale = layer.baseScale * THREE.MathUtils.lerp(1.18 - index * 0.06, 0.9 + index * 0.04, rainMix);
const softness = layer.baseSoftness * THREE.MathUtils.lerp(1.15, 0.82, rainMix) * THREE.MathUtils.lerp(1.0, 1.08, snowMix);
const erosion = layer.baseErosionStrength * THREE.MathUtils.lerp(0.72 + index * 0.08, 1.28 + index * 0.1, rainMix);
const ridge = layer.baseRidgeStrength * THREE.MathUtils.lerp(1.24, 0.78, rainMix) * THREE.MathUtils.lerp(1.0, 0.86, snowMix);
const highlight = layer.baseHighlightStrength * THREE.MathUtils.lerp(1.45 - index * 0.12, 0.62, rainMix) * THREE.MathUtils.lerp(1.12, 0.84, snowMix);
const shadow = layer.baseShadowStrength * THREE.MathUtils.lerp(0.94, 1.36, stormMix);
layer.material.uniforms.layerOpacity.value = opacity;
layer.material.uniforms.cloudCoverage.value = this.params.cloudCoverage;
layer.material.uniforms.cloudDensity.value = this.params.cloudDensity;
layer.material.uniforms.scale.value = layerScale;
layer.material.uniforms.softness.value = softness;
layer.material.uniforms.erosionStrength.value = erosion;
layer.material.uniforms.ridgeStrength.value = ridge;
layer.material.uniforms.highlightStrength.value = highlight;
layer.material.uniforms.shadowStrength.value = shadow;
layer.material.uniforms.tintColor.value.copy(cloudColor);
layer.material.uniforms.lightDir.value.copy(lightDir);
layer.mesh.position.y = layer.baseY - heightDrop + snowLift + dawnMix * index * 18.0;
layer.mesh.visible = opacity > 0.015; layer.mesh.visible = opacity > 0.015;
} });
} }
updateFog() { updateFog() {
@@ -1571,10 +1708,8 @@ export class OceanScene {
} }
if (this.cloudGroup) { if (this.cloudGroup) {
this.cloudGroup.rotation.y = time * 0.004;
this.cloudLayers.forEach((layer) => { this.cloudLayers.forEach((layer) => {
layer.texture.offset.x = time * layer.speedX; layer.material.uniforms.time.value = time;
layer.texture.offset.y = time * layer.speedY;
}); });
} }