CLOUD_GLSL: "\n float cw_hash(vec2 p) {\n return fract(sin(dot(p, vec2(157.31, 269.53))) * 43758.5453);\n }\n float cw_noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n return mix(mix(cw_hash(i), cw_hash(i + vec2(1,0)), f.x),\n mix(cw_hash(i + vec2(0,1)), cw_hash(i + vec2(1,1)), f.x), f.y);\n }\n // Raw cloud density 0..1 at a world XZ point. offset is the accumulated\n // wind drift in world units; scale is the cloud feature size.\n float cloudField(vec2 worldXZ, vec2 offset, float scale) {\n vec2 uv = (worldXZ + offset) / max(scale, 1e-3);\n float n = cw_noise(uv) * 0.65 + cw_noise(uv * 2.63 + 19.17) * 0.35;\n // Two-octave value noise clusters tightly around 0.5 — stretch it toward\n // a uniform 0..1 spread so cloudMask's threshold selects roughly the\n // intended coverage fraction and clouds have real contrast. Without this\n // the shadows read as faint grey mush and the precipitation gate (which\n // thresholds high into the field) barely lets anything fall.\n return smoothstep(0.30, 0.70, n);\n }\n // Threshold the field so ~coverage of the ground is under cloud, with a\n // soft edge. coverage 0 -> no clouds, 1 -> overcast.\n float cloudMask(float field, float coverage) {\n float threshold = 1.0 - coverage;\n return smoothstep(threshold, threshold + 0.18, field);\n }\n" = ...
Shared cloud-field GLSL. The SAME field is sampled by the terrain shader (to darken ground under clouds) and by PrecipitationLayer (to gate where rain/snow falls), so precipitation visibly falls under the drifting clouds that shade the terrain. Because the two consumers use different coverage thresholds, light clouds can shade the ground without raining — only the denser cores precipitate.