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:
co-authored by
GPT-5.6 Sol (Codex)
parent
db23b19a87
commit
e96e1a5f52
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user