// Shared helpers of the Luneraria material family (G2): hashes, value noise, an ordered 4x4 // Bayer threshold for the occlusion fade, and a bump helper from a scalar height. float hash13(vec3 p) { p = fract(p * .1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); } float hash12(vec2 p) { vec3 q = fract(vec3(p.xyx) * .1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y) * q.z); } float noise3(vec3 p) { vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f); return mix(mix(mix(hash13(i), hash13(i + vec3(1, 0, 0)), f.x), mix(hash13(i + vec3(0, 1, 0)), hash13(i + vec3(1, 1, 0)), f.x), f.y), mix(mix(hash13(i + vec3(0, 0, 1)), hash13(i + vec3(1, 0, 1)), f.x), mix(hash13(i + vec3(0, 1, 1)), hash13(i + vec3(1, 1, 1)), f.x), f.y), f.z); } float fbm3(vec3 p) { return noise3(p) * .55 + noise3(p * 2.03 + 7.1) * .3 + noise3(p * 4.11 + 3.3) * .15; } const float BAYER[16] = float[16](0., 8., 2., 10., 12., 4., 14., 6., 3., 11., 1., 9., 15., 7., 13., 5.); float bayer4(vec2 frag) { ivec2 i = ivec2(mod(floor(frag), 4.0)); return (BAYER[i.x + i.y * 4] + .5) / 16.0; } // True when a fragment above the fade floor should be dropped for the occlusion fade. Callers // skip it in the directional shadow pass (orthographic projection), so shadows never dither. bool faded(float amount, float floor_y, float world_y, vec2 frag) { if (amount <= .001 || world_y <= floor_y) return false; return bayer4(frag) < amount * smoothstep(floor_y, floor_y + .8, world_y); }