[claude] Procedural cloud layer below the floating island (#256) #339

Merged
claude merged 1 commits from claude/issue-256 into main 2026-03-24 05:07:04 +00:00

58
app.js
View File

@@ -384,10 +384,9 @@ const CloudShader = {
uniform float uDensity;
varying vec3 vWorldPosition;
// Perlin noise function (simplified, 3D)
// Source: https://thebookofshaders.com/11/
vec3 mod289(vec3 x) { return x - floor(x / 289.0) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x / 289.0) * 289.0; }
// 3D Simplex noise — Ian McEwan / Stefan Gustavson implementation
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
@@ -412,49 +411,48 @@ const CloudShader = {
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 h = 1.0 - abs(x_ - ns.x) - abs(y_ - ns.y);
vec4 b0 = vec4(x_.xy, y_.xy);
vec4 b1 = vec4(x_.zw, y_.zw);
vec4 x = x_ * ns.x + ns.yyyy;
vec4 y = y_ * ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0 - floor(b0);
vec4 a1 = b1 - floor(b1);
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a1.xy, h.y);
vec3 p2 = vec3(a0.zw, h.z);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}
void main() {
// Offset by time for animation
vec3 noiseCoord = vWorldPosition * uNoiseScale + vec3(0.0, uTime * 0.005, uTime * 0.003);
// Drift clouds slowly over time
vec3 noiseCoord = vWorldPosition * uNoiseScale + vec3(uTime * 0.003, 0.0, uTime * 0.002);
// Combine multiple octaves of noise for a more detailed cloud
float noiseVal = snoise(noiseCoord * 1.0) * 0.5;
noiseVal += snoise(noiseCoord * 2.0) * 0.25;
// fBm: three octaves for wispy detail
float noiseVal = snoise(noiseCoord) * 0.500;
noiseVal += snoise(noiseCoord * 2.0) * 0.250;
noiseVal += snoise(noiseCoord * 4.0) * 0.125;
noiseVal = noiseVal / (1.0 + 0.5 + 0.25 + 0.125); // Normalize
noiseVal /= 0.875; // normalise to ~[-1, 1]
// Remap noise to a more cloud-like density curve
float density = smoothstep(0.3, 1.0, noiseVal * 0.5 + 0.5); // 0.3-1.0 to give more wispy edges
// Cloud-like density curve — discard sparse regions
float density = smoothstep(0.25, 0.85, noiseVal * 0.5 + 0.5);
density *= uDensity;
// Make clouds fade out towards the top and bottom of the layer
float yPosNormalized = (vWorldPosition.y - (CLOUD_LAYER_Y - CLOUD_THICKNESS / 2.0)) / CLOUD_THICKNESS;
float fadeFactor = 1.0 - smoothstep(0.0, 0.2, yPosNormalized) * smoothstep(1.0, 0.8, yPosNormalized);
// Fade out towards top and bottom of the slab
float layerBottom = ${(CLOUD_LAYER_Y - CLOUD_THICKNESS * 0.5).toFixed(1)};
float yNorm = (vWorldPosition.y - layerBottom) / ${CLOUD_THICKNESS.toFixed(1)};
float fadeFactor = smoothstep(0.0, 0.15, yNorm) * smoothstep(1.0, 0.85, yNorm);
gl_FragColor = vec4(uCloudColor, density * fadeFactor * CLOUD_OPACITY);
if (gl_FragColor.a < 0.05) discard; // Don't render very transparent pixels
gl_FragColor = vec4(uCloudColor, density * fadeFactor * ${CLOUD_OPACITY.toFixed(1)});
if (gl_FragColor.a < 0.04) discard;
}
`,
};