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)
This commit is contained in:
pkuzic
2026-09-25 14:47:33 +01:00
co-authored by GPT-5.6 Sol (Codex)
parent db23b19a87
commit e96e1a5f52
3 changed files with 133 additions and 127 deletions
@@ -219,10 +219,10 @@ fn water_gerstner(p: vec2<f32>, t: f32, shore: f32) -> vec4<f32> {
let energy = clamp(water.mode.y, 0.0, 1.0);
let tempo = mix(0.4, 1.0, sqrt(energy));
var displacement = vec3<f32>(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<f32>(cos(wave.x), sin(wave.x));
@@ -234,12 +234,12 @@ fn water_gerstner(p: vec2<f32>, t: f32, shore: f32) -> vec4<f32> {
displacement += vec3<f32>(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<f32>(displacement, fold);
}
@@ -251,7 +251,7 @@ fn water_gerstner(p: vec2<f32>, t: f32, shore: f32) -> vec4<f32> {
// 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<f32>) -> vec2<f32> {
fn ripple_hash(p: vec2<f32>) -> vec2<f32> {
var q = fract(vec3<f32>(p.xyx) * vec3<f32>(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<f32>, footprint: f32, distance: f32, room:
for (var i = -1; i <= 1; i += 1) {
let cell = base + vec2<f32>(f32(i), f32(j));
let seed = cell + vec2<f32>(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<f32>) -> vec2<f32> {
fn caustic_edges(p: vec2<f32>, 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>(f32(i), f32(j));
@@ -373,10 +373,15 @@ fn caustic_edges(p: vec2<f32>, t: f32) -> f32 {
let o = 0.5 + 0.38 * sin(vec2<f32>(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<f32>, deep: vec4<f32>) -> 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<f32>, deep: vec4<f32>) -> 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<f32>, deep: vec4<f32>) -> 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<f32>(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<f32>, deep: vec4<f32>) -> 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) {
@@ -388,25 +388,25 @@ fn torch_map_shadow(
if (dot(normal, normal) > 1e-12) {
let a = select(vec3<f32>(1.0, 0.0, 0.0), vec3<f32>(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<f32>(t1, 0.0);
let c2 = view_proj * vec4<f32>(t2, 0.0);
let clip_first = view_proj * vec4<f32>(tangent_first, 0.0);
let clip_second = view_proj * vec4<f32>(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<f32>(0.5, -0.5); // the same ndc -> uv flip `uv` above uses
let u2 = g2.xy * vec2<f32>(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<f32>(0.5, -0.5); // the same ndc -> uv flip `uv` above uses
let uv_second = grad_second.xy * vec2<f32>(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<f32>((g1.z * u2.y - u1.y * g2.z) * inv,
(u1.x * g2.z - g1.z * u2.x) * inv);
grad = vec2<f32>((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<f32>(-1.0, -1.0) * search;
let s1 = vec2<f32>( 1.0, -1.0) * search;
let s2 = vec2<f32>(-1.0, 1.0) * search;
let s3 = vec2<f32>( 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<f32>(-1.0, -1.0) * search;
let search_offset_b = vec2<f32>( 1.0, -1.0) * search;
let search_offset_c = vec2<f32>(-1.0, 1.0) * search;
let search_offset_d = vec2<f32>( 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<f32>(-0.5, -0.5) * texel;
let p1 = vec2<f32>( 0.5, -0.5) * texel;
let p2 = vec2<f32>(-0.5, 0.5) * texel;
let p3 = vec2<f32>( 0.5, 0.5) * texel;
let pcf_offset_a = vec2<f32>(-0.5, -0.5) * texel;
let pcf_offset_b = vec2<f32>( 0.5, -0.5) * texel;
let pcf_offset_c = vec2<f32>(-0.5, 0.5) * texel;
let pcf_offset_d = vec2<f32>( 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<f32>(-1.0, 0.0) * texel;
let p5 = vec2<f32>( 1.0, 0.0) * texel;
let p6 = vec2<f32>( 0.0, -1.0) * texel;
let p7 = vec2<f32>( 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<f32>(-1.0, 0.0) * texel;
let pcf_offset_f = vec2<f32>( 1.0, 0.0) * texel;
let pcf_offset_g = vec2<f32>( 0.0, -1.0) * texel;
let pcf_offset_h = vec2<f32>( 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
+56 -55
View File
@@ -23,7 +23,7 @@ fn linear_to_srgb(c: vec3<f32>) -> vec3<f32> {
// ---- hashes and noise -------------------------------------------------------------------------
// pcg3d (Jarzynski & Olano 2020): three well-mixed u32 from three.
fn pcg3(v_in: vec3<u32>) -> vec3<u32> {
fn pcg_mix(v_in: vec3<u32>) -> vec3<u32> {
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<u32>) -> vec3<u32> {
return v;
}
fn hash33(p: vec3<i32>) -> vec3<f32> {
return vec3<f32>(pcg3(bitcast<vec3<u32>>(p)) >> vec3<u32>(8u)) * (1.0 / 16777216.0);
fn hash_three(p: vec3<i32>) -> vec3<f32> {
return vec3<f32>(pcg_mix(bitcast<vec3<u32>>(p)) >> vec3<u32>(8u)) * (1.0 / 16777216.0);
}
fn hash_cell2(p: vec2<i32>, salt: u32) -> f32 {
return f32(pcg3(vec3<u32>(bitcast<vec2<u32>>(p), salt)).x >> 8u) * (1.0 / 16777216.0);
fn hash_cell(p: vec2<i32>, salt: u32) -> f32 {
return f32(pcg_mix(vec3<u32>(bitcast<vec2<u32>>(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<f32>, salt: u32) -> f32 {
fn value_noise_plane(p: vec2<f32>, salt: u32) -> f32 {
let i = vec2<i32>(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<i32>(1, 0), salt);
let c = hash_cell2(i + vec2<i32>(0, 1), salt);
let d = hash_cell2(i + vec2<i32>(1, 1), salt);
let a = hash_cell(i, salt);
let b = hash_cell(i + vec2<i32>(1, 0), salt);
let c = hash_cell(i + vec2<i32>(0, 1), salt);
let d = hash_cell(i + vec2<i32>(1, 1), salt);
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
fn value_noise3(p: vec3<f32>) -> f32 {
fn value_noise_space(p: vec3<f32>) -> f32 {
let i = vec3<i32>(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<i32>(1, 0, 0)).x;
let n010 = hash33(i + vec3<i32>(0, 1, 0)).x;
let n110 = hash33(i + vec3<i32>(1, 1, 0)).x;
let n001 = hash33(i + vec3<i32>(0, 0, 1)).x;
let n101 = hash33(i + vec3<i32>(1, 0, 1)).x;
let n011 = hash33(i + vec3<i32>(0, 1, 1)).x;
let n111 = hash33(i + vec3<i32>(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<i32>(1, 0, 0)).x;
let noise_aba = hash_three(i + vec3<i32>(0, 1, 0)).x;
let noise_bba = hash_three(i + vec3<i32>(1, 1, 0)).x;
let noise_aab = hash_three(i + vec3<i32>(0, 0, 1)).x;
let noise_bab = hash_three(i + vec3<i32>(1, 0, 1)).x;
let noise_abb = hash_three(i + vec3<i32>(0, 1, 1)).x;
let noise_bbb = hash_three(i + vec3<i32>(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>) -> f32 {
fn fbm_space(p: vec3<f32>) -> 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<f32>(1.7, 9.2, 3.4);
a *= 0.5;
}
@@ -94,13 +94,13 @@ const STOP_ELEV = array<f32, 6>(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<f32>, d1: vec3<f32>, h0: f32, h1: f32) -> vec3<f32> {
let w1 = 2.0 * h1 + h0;
let w2 = h1 + 2.0 * h0;
let safe0 = select(d0, vec3<f32>(1.0), d0 == vec3<f32>(0.0));
let safe1 = select(d1, vec3<f32>(1.0), d1 == vec3<f32>(0.0));
let m = (w1 + w2) / (w1 / safe0 + w2 / safe1);
return select(vec3<f32>(0.0), m, d0 * d1 > vec3<f32>(0.0));
fn pchip_tangent(first_delta: vec3<f32>, second_delta: vec3<f32>, first_span: f32, second_span: f32) -> vec3<f32> {
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<f32>(1.0), first_delta == vec3<f32>(0.0));
let right_safe = select(second_delta, vec3<f32>(1.0), second_delta == vec3<f32>(0.0));
let m = (left_weight + right_weight) / (left_weight / left_safe + right_weight / right_safe);
return select(vec3<f32>(0.0), m, first_delta * second_delta > vec3<f32>(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<vec3<f32>, 6>) -> vec3<f32> {
k = i;
}
}
var m0 = vec3<f32>(0.0);
var m1 = vec3<f32>(0.0);
var tangent_start = vec3<f32>(0.0);
var tangent_end = vec3<f32>(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>) -> f32 {
let h = hash33(vec3<i32>(vec2<i32>(frag), 7));
let h = hash_three(vec3<i32>(vec2<i32>(frag), 7));
return (h.x + h.y - 1.0) / 255.0;
}
@@ -156,10 +157,10 @@ fn dither_tri(frag: vec2<f32>) -> 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<f32>, t: f32, px: f32, band: f32) -> vec3<f32> {
for (var i = 0u; i < 8u; i++) {
let off = vec3<f32>(f32(i & 1u), f32((i >> 1u) & 1u), f32((i >> 2u) & 1u)) * o;
let cell = vec3<i32>(base + off);
let h = hash33(cell);
let h = hash_three(cell);
if (h.x > density) {
continue;
}
let j = hash33(cell + vec3<i32>(7919, 104729, 1299709));
let j = hash_three(cell + vec3<i32>(7919, 104729, 1299709));
let centre = normalize(vec3<f32>(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<f32>, band: f32) -> vec3<f32> {
return vec3<f32>(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<f32>(0.72, 0.8, 1.0) * v;
@@ -245,7 +246,7 @@ fn cloud_billow(p: vec2<f32>, 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<f32>(3.7, 1.9);
@@ -257,7 +258,7 @@ fn cloud_billow(p: vec2<f32>, octaves: i32) -> f32 {
// is read, which turns thresholded blobs into cauliflower edges.
fn cloud_detail(uv: vec2<f32>, t: f32, octaves: i32) -> f32 {
let p = uv * 36.0 + vec2<f32>(0.011, 0.0043) * t;
let w = vec2<f32>(value_noise2(p * 0.5, 173u), value_noise2(p * 0.5 + 5.3, 219u)) - 0.5;
let w = vec2<f32>(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);
}