diff --git a/app.js b/app.js index d30e157..b849d14 100644 --- a/app.js +++ b/app.js @@ -384,77 +384,75 @@ 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; } - vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } + // 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) { const vec2 C = vec2(1.0/6.0, 1.0/3.0); const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); - vec3 i = floor(v + dot(v, C.yyy)); + vec3 i = floor(v + dot(v, C.yyy)); vec3 x0 = v - i + dot(i, C.xxx); - vec3 g = step(x0.yzx, x0.xyz); - vec3 l = 1.0 - g; + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy); vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; vec3 x3 = x0 - D.yyy; i = mod289(i); - vec4 p = permute( permute( permute( - i.z + vec4(0.0, i1.z, i2.z, 1.0 )) - + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) - + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + vec4 p = permute(permute(permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0)) + + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); float n_ = 0.142857142857; - vec3 ns = n_ * D.wyz - D.xzx; - vec4 j = p - 49.0 * floor(p * ns.z * ns.z); + vec3 ns = n_ * D.wyz - D.xzx; + 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 s0 = floor(b0)*2.0 + 1.0; - vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 y_ = floor(j - 7.0 * x_); + 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; - noiseVal += snoise(noiseCoord * 4.0) * 0.125; - noiseVal = noiseVal / (1.0 + 0.5 + 0.25 + 0.125); // Normalize + // 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 /= 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); - - gl_FragColor = vec4(uCloudColor, density * fadeFactor * CLOUD_OPACITY); - if (gl_FragColor.a < 0.05) discard; // Don't render very transparent pixels + // 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.toFixed(1)}); + if (gl_FragColor.a < 0.04) discard; } `, };