From e96e1a5f52e8a2fc7cb2c20ea8756595487e924d Mon Sep 17 00:00:00 2001 From: pkuzic Date: Fri, 25 Sep 2026 14:47:33 +0100 Subject: [PATCH] MONKEY (integration): legalize composable WGSL identifiers Naga Oil treats a trailing digit as an invalid composable-module identifier. Rename the remaining sky, water, and shadow helpers and locals without changing their calculations. Co-Authored-By: GPT-5.6 Sol (Codex) --- .../src/shaders/enhanced_water.wgsl | 55 +++++---- .../src/shaders/shadow_hook.wgsl | 94 +++++++-------- crates/benilla-world/src/shaders/sky_fx.wgsl | 111 +++++++++--------- 3 files changed, 133 insertions(+), 127 deletions(-) diff --git a/crates/benilla-assets/src/shaders/enhanced_water.wgsl b/crates/benilla-assets/src/shaders/enhanced_water.wgsl index 303d27a2..990bb420 100644 --- a/crates/benilla-assets/src/shaders/enhanced_water.wgsl +++ b/crates/benilla-assets/src/shaders/enhanced_water.wgsl @@ -219,10 +219,10 @@ fn water_gerstner(p: vec2, t: f32, shore: f32) -> vec4 { let energy = clamp(water.mode.y, 0.0, 1.0); let tempo = mix(0.4, 1.0, sqrt(energy)); var displacement = vec3(0.0); - var j00 = 1.0; - var j01 = 0.0; - var j10 = 0.0; - var j11 = 1.0; + var jacobian_xx = 1.0; + var jacobian_xz = 0.0; + var jacobian_zx = 0.0; + var jacobian_zz = 1.0; for (var i = 0u; i < 2u; i += 1u) { let wave = WATER_WAVES[i]; let direction = vec2(cos(wave.x), sin(wave.x)); @@ -234,12 +234,12 @@ fn water_gerstner(p: vec2, t: f32, shore: f32) -> vec4 { displacement += vec3(direction.x * horizontal * cos(phase), amplitude * sin(phase), direction.y * horizontal * cos(phase)); let compression = horizontal * k * sin(phase); - j00 -= compression * direction.x * direction.x; - j01 -= compression * direction.x * direction.y; - j10 -= compression * direction.y * direction.x; - j11 -= compression * direction.y * direction.y; + jacobian_xx -= compression * direction.x * direction.x; + jacobian_xz -= compression * direction.x * direction.y; + jacobian_zx -= compression * direction.y * direction.x; + jacobian_zz -= compression * direction.y * direction.y; } - let fold = max(1.0 - (j00 * j11 - j01 * j10), 0.0); + let fold = max(1.0 - (jacobian_xx * jacobian_zz - jacobian_xz * jacobian_zx), 0.0); return vec4(displacement, fold); } @@ -251,7 +251,7 @@ fn water_gerstner(p: vec2, t: f32, shore: f32) -> vec4 { // clock is MonkeyFrame `wet_a.z` (wraps at 1000 s; the rates are multiples of 1/1000, so the wrap is // seamless). Dry (`wet_a.x == 0`), WMO interior pools, interior-fog rooms, and pixels too far or too // coarse to hold a ring return zero. -fn ripple_hash2(p: vec2) -> vec2 { +fn ripple_hash(p: vec2) -> vec2 { var q = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973)); q += dot(q, q.yzx + 33.33); return fract((q.xx + q.yz) * q.zy); @@ -275,16 +275,16 @@ fn rain_ripple_normal(world_xz: vec2, footprint: f32, distance: f32, room: for (var i = -1; i <= 1; i += 1) { let cell = base + vec2(f32(i), f32(j)); let seed = cell + vec2(f32(layer) * 57.0, f32(layer) * 113.0); - let h = ripple_hash2(seed); - let h2 = ripple_hash2(seed + 19.19); + let h = ripple_hash(seed); + let h_alt = ripple_hash(seed + 19.19); // Each period re-rolls whether this cell rains, so the pattern does not repeat. let cycle = wet_row.z * rate + h.x; - let roll = ripple_hash2(seed + floor(cycle) * 7.31).x; + let roll = ripple_hash(seed + floor(cycle) * 7.31).x; if roll > rain * 0.85 { continue; } let phase = fract(cycle); - let centre = (cell + 0.5 + (h2 - 0.5) * 0.6) * c; + let centre = (cell + 0.5 + (h_alt - 0.5) * 0.6) * c; let to_p = world_xz - centre; let d = length(to_p); let radius = phase * 0.6 * c; @@ -363,8 +363,8 @@ fn caustic_point(cell: vec2) -> vec2 { fn caustic_edges(p: vec2, t: f32) -> f32 { let cell = floor(p); let f = fract(p); - var d1 = 8.0; - var d2 = 8.0; + var nearest_distance = 8.0; + var next_distance = 8.0; for (var j = -1; j <= 1; j += 1) { for (var i = -1; i <= 1; i += 1) { let g = vec2(f32(i), f32(j)); @@ -373,10 +373,15 @@ fn caustic_edges(p: vec2, t: f32) -> f32 { let o = 0.5 + 0.38 * sin(vec2(t * 0.83, t * 0.71) + 6.2831853 * h); let r = g + o - f; let d = dot(r, r); - if d < d1 { d2 = d1; d1 = d; } else if d < d2 { d2 = d; } + if d < nearest_distance { + next_distance = nearest_distance; + nearest_distance = d; + } else if d < next_distance { + next_distance = d; + } } } - return sqrt(d2) - sqrt(d1); + return sqrt(next_distance) - sqrt(nearest_distance); } // Two webs at incommensurate scales and rates: the fine one draws the lines, the coarse one sets @@ -554,7 +559,7 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec // `+ t/period` with a crest-count that RISES with depth ⇒ a crest of fixed phase slides to // shallower water as time runs: the train travels shoreward. let shore_phase = 6.2831853 * (shore_g + t / SHORE_PERIOD); - let shore_phase2 = 6.2831853 * (1.9 * shore_g + t / (SHORE_PERIOD * 0.62)) + 2.1; + let shore_phase_secondary = 6.2831853 * (1.9 * shore_g + t / (SHORE_PERIOD * 0.62)) + 2.1; // Steepness rises as it shoals (3.5 → 0.75 yd), collapses into the break below ~0.35 yd, and // is gone past the band's offshore edge. let shoal = smoothstep(3.5, 0.75, vertical_depth); @@ -579,7 +584,7 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec // and a wavenumber, so a gentle beach and a steep one roll with the same visible strength // instead of one washing out and the other exploding. let shore_grad = SHORE_TILT * shore_gain - * (cos(shore_phase) + 0.45 * cos(shore_phase2)) * offshore; + * (cos(shore_phase) + 0.45 * cos(shore_phase_secondary)) * offshore; let shore = select(1.0, swell_shore_fade(in.depth), ocean_mesh); let wave = water_waves(p, t, length(eye_pos), footprint, shore, false); @@ -642,7 +647,7 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec // it9-water-beach-top). The PHASE is a function of the depth itself and is continuous, so a term // driven by the phase alone cannot facet. The tilt stays at 0.09 for a little specular life. let shore_crest = pow(max(sin(shore_phase), 0.0), 2.0) - + 0.45 * pow(max(sin(shore_phase2), 0.0), 2.0); + + 0.45 * pow(max(sin(shore_phase_secondary), 0.0), 2.0); rgb += shore_gain * shore_crest * 0.13 * lighting * vec3(0.72, 0.95, 0.90); if !ocean_mesh { // Keep the zone's green-blue absorption under warm dusk illumination. @@ -758,17 +763,17 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec let interior_fixture = colour.w >= 0.5; if interior_fixture != (water.lane.x > 1.5) { continue; } let delta = water_light[21u + 2u * i].xyz - in.world_position.xyz; - let d2 = dot(delta, delta); - if d2 >= distances[3] { continue; } + let squared_distance = dot(delta, delta); + if squared_distance >= distances[3] { continue; } var slot = 3u; loop { if slot == 0u { break; } - if d2 >= distances[slot - 1u] { break; } + if squared_distance >= distances[slot - 1u] { break; } distances[slot] = distances[slot - 1u]; nearest[slot] = nearest[slot - 1u]; slot -= 1u; } - distances[slot] = d2; + distances[slot] = squared_distance; nearest[slot] = i; } for (var j = 0u; j < 4u; j += 1u) { diff --git a/crates/benilla-assets/src/shaders/shadow_hook.wgsl b/crates/benilla-assets/src/shaders/shadow_hook.wgsl index d7b3206f..797a5665 100644 --- a/crates/benilla-assets/src/shaders/shadow_hook.wgsl +++ b/crates/benilla-assets/src/shaders/shadow_hook.wgsl @@ -388,25 +388,25 @@ fn torch_map_shadow( if (dot(normal, normal) > 1e-12) { let a = select(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), abs(normal.x) > abs(normal.z)); - let t1 = cross(normal, a); - let t2 = cross(normal, t1); + let tangent_first = cross(normal, a); + let tangent_second = cross(normal, tangent_first); // d(clip) along each tangent, then through the perspective divide: // `d(ndc) = (d(clip).xyz - ndc * d(clip).w) / clip.w`. - let c1 = view_proj * vec4(t1, 0.0); - let c2 = view_proj * vec4(t2, 0.0); + let clip_first = view_proj * vec4(tangent_first, 0.0); + let clip_second = view_proj * vec4(tangent_second, 0.0); let inv_w = 1.0 / clip.w; - let g1 = (c1.xyz - ndc * c1.w) * inv_w; - let g2 = (c2.xyz - ndc * c2.w) * inv_w; - let u1 = g1.xy * vec2(0.5, -0.5); // the same ndc -> uv flip `uv` above uses - let u2 = g2.xy * vec2(0.5, -0.5); + let grad_first = (clip_first.xyz - ndc * clip_first.w) * inv_w; + let grad_second = (clip_second.xyz - ndc * clip_second.w) * inv_w; + let uv_first = grad_first.xy * vec2(0.5, -0.5); // the same ndc -> uv flip `uv` above uses + let uv_second = grad_second.xy * vec2(0.5, -0.5); // Solve `dot(grad, u_i) = g_i.z` for i = 1, 2. `det` collapses only as the plane goes // EDGE-ON to the fixture, where the plane projects to a line in the map, `max(N·L, 0)` has // already taken the lit term to zero, and a zero gradient is the right answer anyway. - let det = u1.x * u2.y - u1.y * u2.x; + let det = uv_first.x * uv_second.y - uv_first.y * uv_second.x; if (abs(det) > 1e-9) { let inv = 1.0 / det; - grad = vec2((g1.z * u2.y - u1.y * g2.z) * inv, - (u1.x * g2.z - g1.z * u2.x) * inv); + grad = vec2((grad_first.z * uv_second.y - uv_first.y * grad_second.z) * inv, + (uv_first.x * grad_second.z - grad_first.z * uv_second.x) * inv); } } // …and the ceiling on what any one tap may be moved by. See `TORCH_SLOPE_MAX`. @@ -420,24 +420,24 @@ fn torch_map_shadow( let search = base * TORCH_PCSS_SEARCH * inv_dims; // MONKEY (slope bias): the offsets are NAMED so the search's reference and its sample are the // same displacement, exactly as in the PCF kernel below. - let s0 = vec2(-1.0, -1.0) * search; - let s1 = vec2( 1.0, -1.0) * search; - let s2 = vec2(-1.0, 1.0) * search; - let s3 = vec2( 1.0, 1.0) * search; - let b0 = torch_map_depth(depth_tex, layer, uv + s0, dims); - let b1 = torch_map_depth(depth_tex, layer, uv + s1, dims); - let b2 = torch_map_depth(depth_tex, layer, uv + s2, dims); - let b3 = torch_map_depth(depth_tex, layer, uv + s3, dims); + let search_offset_a = vec2(-1.0, -1.0) * search; + let search_offset_b = vec2( 1.0, -1.0) * search; + let search_offset_c = vec2(-1.0, 1.0) * search; + let search_offset_d = vec2( 1.0, 1.0) * search; + let blocker_a = torch_map_depth(depth_tex, layer, uv + search_offset_a, dims); + let blocker_b = torch_map_depth(depth_tex, layer, uv + search_offset_b, dims); + let blocker_c = torch_map_depth(depth_tex, layer, uv + search_offset_c, dims); + let blocker_d = torch_map_depth(depth_tex, layer, uv + search_offset_d, dims); var blocker = 0.0; var blockers = 0.0; // MONKEY (slope bias): the SEARCH is plane-corrected too, and it has to be. Left on the flat // reference, a grazing floor detects ITSELF as its own blocker, and the penumbra width below is // then estimated from a depth difference that is pure bias error — a pool that goes soft with // nothing in it. Same law, same `grad`, same clamp as the taps. - if (b0 > torch_plane_ref(ref_depth, grad, s0, slope_lim)) { blocker += b0; blockers += 1.0; } - if (b1 > torch_plane_ref(ref_depth, grad, s1, slope_lim)) { blocker += b1; blockers += 1.0; } - if (b2 > torch_plane_ref(ref_depth, grad, s2, slope_lim)) { blocker += b2; blockers += 1.0; } - if (b3 > torch_plane_ref(ref_depth, grad, s3, slope_lim)) { blocker += b3; blockers += 1.0; } + if (blocker_a > torch_plane_ref(ref_depth, grad, search_offset_a, slope_lim)) { blocker += blocker_a; blockers += 1.0; } + if (blocker_b > torch_plane_ref(ref_depth, grad, search_offset_b, slope_lim)) { blocker += blocker_b; blockers += 1.0; } + if (blocker_c > torch_plane_ref(ref_depth, grad, search_offset_c, slope_lim)) { blocker += blocker_c; blockers += 1.0; } + if (blocker_d > torch_plane_ref(ref_depth, grad, search_offset_d, slope_lim)) { blocker += blocker_d; blockers += 1.0; } // MONKEY (pcss) 2/3 — PENUMBRA WIDTH. `radius` stays at `base` when the search found nothing: a // caster thinner than the search box (a chair leg, a tent rope, a candlestick) must NOT be @@ -463,33 +463,33 @@ fn torch_map_shadow( // MONKEY (slope bias): every tap carries its OWN plane-corrected reference. With `grad` zero - // a receiver square-on to the fixture, or a zero normal - `torch_plane_ref` returns `ref_depth` // unchanged and this kernel is the shipped one bit-for-bit. - let p0 = vec2(-0.5, -0.5) * texel; - let p1 = vec2( 0.5, -0.5) * texel; - let p2 = vec2(-0.5, 0.5) * texel; - let p3 = vec2( 0.5, 0.5) * texel; + let pcf_offset_a = vec2(-0.5, -0.5) * texel; + let pcf_offset_b = vec2( 0.5, -0.5) * texel; + let pcf_offset_c = vec2(-0.5, 0.5) * texel; + let pcf_offset_d = vec2( 0.5, 0.5) * texel; var sum = 0.0; - sum += textureSampleCompareLevel(depth_tex, comp, uv + p0, layer, - torch_plane_ref(ref_depth, grad, p0, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p1, layer, - torch_plane_ref(ref_depth, grad, p1, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p2, layer, - torch_plane_ref(ref_depth, grad, p2, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p3, layer, - torch_plane_ref(ref_depth, grad, p3, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_a, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_a, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_b, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_b, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_c, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_c, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_d, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_d, slope_lim)); var pcf = sum * 0.25; if (radius > TORCH_PCSS_WIDE) { - let p4 = vec2(-1.0, 0.0) * texel; - let p5 = vec2( 1.0, 0.0) * texel; - let p6 = vec2( 0.0, -1.0) * texel; - let p7 = vec2( 0.0, 1.0) * texel; - sum += textureSampleCompareLevel(depth_tex, comp, uv + p4, layer, - torch_plane_ref(ref_depth, grad, p4, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p5, layer, - torch_plane_ref(ref_depth, grad, p5, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p6, layer, - torch_plane_ref(ref_depth, grad, p6, slope_lim)); - sum += textureSampleCompareLevel(depth_tex, comp, uv + p7, layer, - torch_plane_ref(ref_depth, grad, p7, slope_lim)); + let pcf_offset_e = vec2(-1.0, 0.0) * texel; + let pcf_offset_f = vec2( 1.0, 0.0) * texel; + let pcf_offset_g = vec2( 0.0, -1.0) * texel; + let pcf_offset_h = vec2( 0.0, 1.0) * texel; + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_e, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_e, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_f, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_f, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_g, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_g, slope_lim)); + sum += textureSampleCompareLevel(depth_tex, comp, uv + pcf_offset_h, layer, + torch_plane_ref(ref_depth, grad, pcf_offset_h, slope_lim)); pcf = sum * 0.125; } // Phase 5: the six cube faces tile the whole sphere, so there is no cone edge to soften — a fade diff --git a/crates/benilla-world/src/shaders/sky_fx.wgsl b/crates/benilla-world/src/shaders/sky_fx.wgsl index e22f3356..e547f90b 100644 --- a/crates/benilla-world/src/shaders/sky_fx.wgsl +++ b/crates/benilla-world/src/shaders/sky_fx.wgsl @@ -23,7 +23,7 @@ fn linear_to_srgb(c: vec3) -> vec3 { // ---- hashes and noise ------------------------------------------------------------------------- // pcg3d (Jarzynski & Olano 2020): three well-mixed u32 from three. -fn pcg3(v_in: vec3) -> vec3 { +fn pcg_mix(v_in: vec3) -> vec3 { var v = v_in * 1664525u + 1013904223u; v.x += v.y * v.z; v.y += v.z * v.x; @@ -35,52 +35,52 @@ fn pcg3(v_in: vec3) -> vec3 { return v; } -fn hash33(p: vec3) -> vec3 { - return vec3(pcg3(bitcast>(p)) >> vec3(8u)) * (1.0 / 16777216.0); +fn hash_three(p: vec3) -> vec3 { + return vec3(pcg_mix(bitcast>(p)) >> vec3(8u)) * (1.0 / 16777216.0); } -fn hash_cell2(p: vec2, salt: u32) -> f32 { - return f32(pcg3(vec3(bitcast>(p), salt)).x >> 8u) * (1.0 / 16777216.0); +fn hash_cell(p: vec2, salt: u32) -> f32 { + return f32(pcg_mix(vec3(bitcast>(p), salt)).x >> 8u) * (1.0 / 16777216.0); } // Value noise with the quintic fade: the cubic's second-derivative seam shows as square plateaus // on a slowly drifting sky (the WarcraftXL note). -fn value_noise2(p: vec2, salt: u32) -> f32 { +fn value_noise_plane(p: vec2, salt: u32) -> f32 { let i = vec2(floor(p)); let f = fract(p); let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); - let a = hash_cell2(i, salt); - let b = hash_cell2(i + vec2(1, 0), salt); - let c = hash_cell2(i + vec2(0, 1), salt); - let d = hash_cell2(i + vec2(1, 1), salt); + let a = hash_cell(i, salt); + let b = hash_cell(i + vec2(1, 0), salt); + let c = hash_cell(i + vec2(0, 1), salt); + let d = hash_cell(i + vec2(1, 1), salt); return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); } -fn value_noise3(p: vec3) -> f32 { +fn value_noise_space(p: vec3) -> f32 { let i = vec3(floor(p)); let f = fract(p); let u = f * f * (3.0 - 2.0 * f); - let n000 = hash33(i).x; - let n100 = hash33(i + vec3(1, 0, 0)).x; - let n010 = hash33(i + vec3(0, 1, 0)).x; - let n110 = hash33(i + vec3(1, 1, 0)).x; - let n001 = hash33(i + vec3(0, 0, 1)).x; - let n101 = hash33(i + vec3(1, 0, 1)).x; - let n011 = hash33(i + vec3(0, 1, 1)).x; - let n111 = hash33(i + vec3(1, 1, 1)).x; - let x00 = mix(n000, n100, u.x); - let x10 = mix(n010, n110, u.x); - let x01 = mix(n001, n101, u.x); - let x11 = mix(n011, n111, u.x); - return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z); + let noise_aaa = hash_three(i).x; + let noise_baa = hash_three(i + vec3(1, 0, 0)).x; + let noise_aba = hash_three(i + vec3(0, 1, 0)).x; + let noise_bba = hash_three(i + vec3(1, 1, 0)).x; + let noise_aab = hash_three(i + vec3(0, 0, 1)).x; + let noise_bab = hash_three(i + vec3(1, 0, 1)).x; + let noise_abb = hash_three(i + vec3(0, 1, 1)).x; + let noise_bbb = hash_three(i + vec3(1, 1, 1)).x; + let interp_aa = mix(noise_aaa, noise_baa, u.x); + let interp_ba = mix(noise_aba, noise_bba, u.x); + let interp_ab = mix(noise_aab, noise_bab, u.x); + let interp_bb = mix(noise_abb, noise_bbb, u.x); + return mix(mix(interp_aa, interp_ba, u.y), mix(interp_ab, interp_bb, u.y), u.z); } -fn fbm3(p: vec3) -> f32 { +fn fbm_space(p: vec3) -> f32 { var s = 0.0; var a = 0.5; var q = p; for (var o = 0; o < 3; o++) { - s += a * value_noise3(q); + s += a * value_noise_space(q); q = q * 2.03 + vec3(1.7, 9.2, 3.4); a *= 0.5; } @@ -94,13 +94,13 @@ const STOP_ELEV = array(0.0, 1.8, 3.7, 9.8, 16.8, 90.0); // Fritsch–Carlson tangent at an interior stop: 0 at a local extremum, else the weighted harmonic // mean of the two secants, so the curve never overshoots a stop (no halos, no new colours). -fn pchip_tangent(d0: vec3, d1: vec3, h0: f32, h1: f32) -> vec3 { - let w1 = 2.0 * h1 + h0; - let w2 = h1 + 2.0 * h0; - let safe0 = select(d0, vec3(1.0), d0 == vec3(0.0)); - let safe1 = select(d1, vec3(1.0), d1 == vec3(0.0)); - let m = (w1 + w2) / (w1 / safe0 + w2 / safe1); - return select(vec3(0.0), m, d0 * d1 > vec3(0.0)); +fn pchip_tangent(first_delta: vec3, second_delta: vec3, first_span: f32, second_span: f32) -> vec3 { + let left_weight = 2.0 * second_span + first_span; + let right_weight = second_span + 2.0 * first_span; + let left_safe = select(first_delta, vec3(1.0), first_delta == vec3(0.0)); + let right_safe = select(second_delta, vec3(1.0), second_delta == vec3(0.0)); + let m = (left_weight + right_weight) / (left_weight / left_safe + right_weight / right_safe); + return select(vec3(0.0), m, first_delta * second_delta > vec3(0.0)); } // Monotone cubic through the six stops (`y[0]` = fog at 0°, `y[5]` = zenith), linear light. The @@ -127,28 +127,29 @@ fn smooth_gradient(elev: f32, y: array, 6>) -> vec3 { k = i; } } - var m0 = vec3(0.0); - var m1 = vec3(0.0); + var tangent_start = vec3(0.0); + var tangent_end = vec3(0.0); if (k > 0) { - m0 = pchip_tangent(d[k - 1], d[k], h[k - 1], h[k]); + tangent_start = pchip_tangent(d[k - 1], d[k], h[k - 1], h[k]); } if (k < 4) { - m1 = pchip_tangent(d[k], d[k + 1], h[k], h[k + 1]); + tangent_end = pchip_tangent(d[k], d[k + 1], h[k], h[k + 1]); } let hk = h[k]; let t = (elev - se[k]) / hk; - let t2 = t * t; - let t3 = t2 * t; - let h00 = 2.0 * t3 - 3.0 * t2 + 1.0; - let h10 = t3 - 2.0 * t2 + t; - let h01 = -2.0 * t3 + 3.0 * t2; - let h11 = t3 - t2; - return h00 * yy[k] + h10 * hk * m0 + h01 * yy[k + 1] + h11 * hk * m1; + let t_sq = t * t; + let t_cube = t_sq * t; + let blend_start = 2.0 * t_cube - 3.0 * t_sq + 1.0; + let blend_start_tangent = t_cube - 2.0 * t_sq + t; + let blend_end = -2.0 * t_cube + 3.0 * t_sq; + let blend_end_tangent = t_cube - t_sq; + return blend_start * yy[k] + blend_start_tangent * hk * tangent_start + + blend_end * yy[k + 1] + blend_end_tangent * hk * tangent_end; } // Triangular dither of ±1 LSB at 8 bits, fixed per pixel (no temporal shimmer). fn dither_tri(frag: vec2) -> f32 { - let h = hash33(vec3(vec2(frag), 7)); + let h = hash_three(vec3(vec2(frag), 7)); return (h.x + h.y - 1.0) / 255.0; } @@ -156,10 +157,10 @@ fn dither_tri(frag: vec2) -> f32 { // Henyey–Greenstein phase normalised to 1 toward the sun. fn hg_norm(c: f32, g: f32) -> f32 { - let g2 = g * g; - let a = 1.0 + g2 - 2.0 * g * c; - let a1 = (1.0 - g) * (1.0 - g); - return pow(a1 / a, 1.5); + let g_sq = g * g; + let a = 1.0 + g_sq - 2.0 * g * c; + let a_min = (1.0 - g) * (1.0 - g); + return pow(a_min / a, 1.5); } // A two-lobe Mie-like halo: a broad aureole plus a tighter core round the disc. @@ -188,11 +189,11 @@ fn star_field(dir: vec3, t: f32, px: f32, band: f32) -> vec3 { for (var i = 0u; i < 8u; i++) { let off = vec3(f32(i & 1u), f32((i >> 1u) & 1u), f32((i >> 2u) & 1u)) * o; let cell = vec3(base + off); - let h = hash33(cell); + let h = hash_three(cell); if (h.x > density) { continue; } - let j = hash33(cell + vec3(7919, 104729, 1299709)); + let j = hash_three(cell + vec3(7919, 104729, 1299709)); let centre = normalize(vec3(cell) + 0.5 + (j - 0.5) * 0.9) * STAR_GRID; let d = length(p - centre); let mag = pow(h.y, 22.0); @@ -224,8 +225,8 @@ fn milky_way(dir: vec3, band: f32) -> vec3 { return vec3(0.0); } let b = dot(dir, normalize(GALAXY_N)); - let cloud = fbm3(dir * 5.0); - let lane = exp(-(b - 0.02) * (b - 0.02) / (2.0 * 0.03 * 0.03)) * smoothstep(0.35, 0.7, fbm3(dir * 9.0 + 11.0)); + let cloud = fbm_space(dir * 5.0); + let lane = exp(-(b - 0.02) * (b - 0.02) / (2.0 * 0.03 * 0.03)) * smoothstep(0.35, 0.7, fbm_space(dir * 9.0 + 11.0)); let core = 1.0 + 1.2 * pow(max(dot(dir, normalize(GALAXY_CORE)), 0.0), 4.0); let v = band * (0.3 + 0.7 * cloud) * (1.0 - 0.75 * lane) * core; return vec3(0.72, 0.8, 1.0) * v; @@ -245,7 +246,7 @@ fn cloud_billow(p: vec2, octaves: i32) -> f32 { var tot = 0.0; var q = p; for (var o = 0; o < octaves; o++) { - s += a * billow(value_noise2(q, u32(o) * 57u)); + s += a * billow(value_noise_plane(q, u32(o) * 57u)); tot += a; a *= 0.55; q = q * 2.0 + vec2(3.7, 1.9); @@ -257,7 +258,7 @@ fn cloud_billow(p: vec2, octaves: i32) -> f32 { // is read, which turns thresholded blobs into cauliflower edges. fn cloud_detail(uv: vec2, t: f32, octaves: i32) -> f32 { let p = uv * 36.0 + vec2(0.011, 0.0043) * t; - let w = vec2(value_noise2(p * 0.5, 173u), value_noise2(p * 0.5 + 5.3, 219u)) - 0.5; + let w = vec2(value_noise_plane(p * 0.5, 173u), value_noise_plane(p * 0.5 + 5.3, 219u)) - 0.5; return cloud_billow(p + w * 2.6, octaves); }