调整云
This commit is contained in:
@@ -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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user