// monolith — "Glitching Monolith" mandelbulb raymarcher. // // A power-N mandelbulb DE raymarched as particle-dithered surface + soft halo // on a deep void; sub-bass breathes its scale (CPU side), mid-band swirls the // feedback trail, hi-band drives a coarse-cell UV-displacement glitch grid + // a simulated frame-rate stutter (the feedback decay floor pins near 1.0 so // the previous frame survives unchanged — looks like the renderer dropped to // ~10 fps for a few frames). Particle aesthetic comes from a per-pixel hash // threshold against the surface intensity: each pixel "is" a particle that // either shows or doesn't, sold by dense fine grain on top. // // Cost bounded the same way breakcore is: bounding-sphere ray test discards // background pixels in one op, march is sphere-traced with the shader's hard // 96-step ceiling, normal (6× map) is gated to pixels actually on-surface. // Bulb iteration count is fixed at 8 (taste — higher melts detail, lower // reads blocky). Pure function of UBO + hash(fragCoord, frame) so `--render` // is bit-reproducible. // // UBO header is **10** std140 rows (40 f32). Rust↔WGSL coupled — change one // ⇒ change the other. Naga only validates this WGSL at pipeline-create on a // real GPU. No nodes array: the form is the DE itself. `col2` carries a // secondary neon accent so the surface can paint two contrasting hues at // once — the bulb never reads as one wash. const PI: f32 = 3.14159265; const BULB_ITERS: i32 = 8; struct U { cam: vec4, // yaw, pitch, roll, dist p0: vec4, // scale, glow_gain, ca_px, edge_softness col0: vec4, // base.rgb, fade col1: vec4, // accent.rgb (primary neon), flash p1: vec4, // res_w, res_h, frame, time p2: vec4, // march_steps, power_n, feedback_on, world_r p3: vec4, // grain, glitch_a, fog, beat p4: vec4, // loud, low, mid, high p5: vec4, // stutter_w, swirl, aspect, tonality col2: vec4, // accent2.rgb (secondary neon), release }; @group(0) @binding(0) var u: U; @group(0) @binding(1) var prev_tex: texture_2d; @group(0) @binding(2) var prev_smp: sampler; struct VsOut { @builtin(position) pos: vec4, @location(0) uv: vec2, }; @vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut { var o: VsOut; let x = f32((vi << 1u) & 2u); let y = f32(vi & 2u); o.uv = vec2(x, y); o.pos = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); return o; } fn hash21(p: vec2) -> f32 { var q = fract(p * vec2(123.34, 345.45)); q = q + dot(q, q + 34.345); return fract(q.x * q.y); } // yaw(Y) -> pitch(X) -> roll(Z), shared convention with breakcore. fn rot(v: vec3) -> vec3 { let sy = sin(u.cam.x); let cy = cos(u.cam.x); let sp = sin(u.cam.y); let cp = cos(u.cam.y); let sr = sin(u.cam.z); let cr = cos(u.cam.z); let x1 = v.x * cy - v.z * sy; let z1 = v.x * sy + v.z * cy; let y2 = v.y * cp - z1 * sp; let z2 = v.y * sp + z1 * cp; let x3 = x1 * cr - y2 * sr; let y3 = x1 * sr + y2 * cr; return vec3(x3, y3, z2); } // Mandelbulb distance estimator (Quilez / Hart). Iterates z_{n+1} = z_n^P + c // in spherical coords; the running `dr` tracks |dz/dz0| so we can return a // proper distance bound 0.5·log(r)·r/dr. fn de_bulb(p0: vec3, power: f32) -> f32 { var z = p0; var dr = 1.0; var r = 0.0; for (var i = 0; i < BULB_ITERS; i = i + 1) { r = length(z); if (r > 2.0) { break; } let theta = acos(clamp(z.z / max(r, 1e-6), -1.0, 1.0)); let phi = atan2(z.y, z.x); dr = pow(r, power - 1.0) * power * dr + 1.0; let zr = pow(r, power); let t2 = theta * power; let p2 = phi * power; z = zr * vec3(sin(t2) * cos(p2), sin(t2) * sin(p2), cos(t2)); z = z + p0; } return 0.5 * log(max(r, 1e-6)) * r / max(dr, 1e-6); } // Scene = scaled bulb. `scl` is the sub-bass-breath spring multiplier. fn map(p: vec3) -> f32 { let scl = u.p0.x; return de_bulb(p * (1.0 / max(scl, 1e-3)), u.p2.y) * scl; } fn calc_normal(p: vec3) -> vec3 { let e = vec2(0.0022, 0.0); return normalize(vec3( map(p + e.xyy) - map(p - e.xyy), map(p + e.yxy) - map(p - e.yxy), map(p + e.yyx) - map(p - e.yyx), )); } @fragment fn fs_main(in: VsOut) -> @location(0) vec4 { let res_w = u.p1.x; let res_h = u.p1.y; let frame = u.p1.z; let glow = u.p0.y; let ca_px = u.p0.z; let edge = u.p0.w; let base = u.col0.xyz; let accent = u.col1.xyz; let accent2 = u.col2.xyz; let fade = u.col0.w; let flash = u.col1.w; let time = u.p1.w; let rb = u.p2.w; // bounding-sphere radius let grain_a = u.p3.x; let glitch = u.p3.y; // 0..~1.2 coarse-cell UV shove (hi-band) let fog = u.p3.z; let beat = u.p3.w; let loud = u.p4.x; let lowf = u.p4.y; // [0..1] sub-bass level — gravity well weight let midf = u.p4.z; // [0..1] mid level — swirl gain let highf = u.p4.w; // [0..1] hi/snare level — rim flicker let stut = u.p5.x; // 0..1 simulated-stutter weight let swirl = u.p5.y; // accumulated swirl angle (rad) let aspect = u.p5.z; let tonal = u.p5.w; let release = u.col2.w; // --- hi-band glitch grid // : coarse-cell UV displacement of the FEEDBACK // sample only — the bulb itself stays geometrically stable so a busy hat // pattern can't tear the whole picture. Deadband below 0.35 so quiet // music produces no glitch at all; only a small fraction of cells shove // (threshold 0.85) so the effect is sparse, not screen-filling. let glitch_eff = max(clamp(glitch, 0.0, 1.0) - 0.35, 0.0) / 0.65; var glitch_off = vec2(0.0); if (glitch_eff > 0.001) { let cells = 18.0 + 14.0 * glitch_eff; let cy = floor(in.uv.y * cells); let cx = floor(in.uv.x * cells); let hold = max(3.0 + 5.0 * (1.0 - glitch_eff), 1.0); let stuck_frame = floor(frame / hold); let pick = step(0.85, hash21(vec2(cx + cy * 7.0, stuck_frame))); let sx = (hash21(vec2(cx * 3.7, cy + stuck_frame)) - 0.5) * 0.045 * glitch_eff * pick; let sy = (hash21(vec2(cx + cy * 11.0, stuck_frame * 2.0)) - 0.5) * 0.020 * glitch_eff * pick; glitch_off = vec2(sx, sy); } let uv = in.uv; let ndc = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0); let dist = u.cam.w; let focal = 1.6 + 0.4 * release * release; // FOV warp on drop let ro = vec3(0.0, 0.0, -dist); let rd = normalize(vec3(ndc.x * aspect, ndc.y, focal)); // Ray vs bounding sphere let b = dot(ro, rd); let c = dot(ro, ro) - rb * rb; let disc = b * b - c; var col = vec3(0.0); var inten = 0.0; var hit_t = -1.0; var dmin = 1e9; if (disc > 0.0) { let sq = sqrt(disc); var t = max(-b - sq, 0.0); let t_end = -b + sq; let min_step = max(t_end - t, 1e-3) / 96.0; let steps = min(i32(u.p2.x), 96); for (var s = 0; s < steps; s = s + 1) { let d = map(rot(ro + rd * t)); if (d < dmin) { dmin = d; } if (d < 0.0018) { hit_t = t; break; } t = t + max(d * 0.80, min_step); if (t > t_end) { break; } } // Tight dust halo from closest approach (not unbounded accumulation, // so dense regions can't white-out). The wider tail is intentionally // dim — a noisy/atonal song widens it via `edge`, tonal stays crisp. let dl = max(dmin, 0.0); let halo = exp(-dl * dl * 7000.0 * edge) + 0.02 * exp(-dl * 30.0); let tt = select(t, hit_t, hit_t > 0.0); let depth = clamp((tt + b) / max(2.0 * sq, 1e-3), 0.0, 1.0); inten = clamp(halo * glow * (1.0 - fog * depth), 0.0, 1.0); if (inten > 0.015) { let rp = rot(ro + rd * tt); // Position-dependent **tint mix** across the bulb surface — a // slow standing wave through (x,y,z) so neighbouring regions // read as different neons. Drifts on time so the colour // distribution evolves without snapping. This is what keeps the // frame from being one hue: half the bulb sits closer to // `accent`, the other half closer to `accent2`. let tint = 0.5 + 0.5 * sin(rp.x * 3.2 + rp.y * 2.4 + rp.z * 1.8 + time * 0.30); let acc_mix = mix(accent, accent2, tint); // Surface shade — gated to genuinely on-surface pixels so the // 6×map() normal cost can't run over the entire halo screen. // Accent contributions are deliberately small: the body stays // brutalist-grey, the neon shows only where it earns its keep // (fresnel edge + hi-band onset rim). var body = base * 0.55; if (dmin < 0.010) { let n = calc_normal(rp); let vdir = normalize(rot(-rd)); let lambert = clamp(dot(n, normalize(vec3(0.4, 0.7, -0.55))), 0.0, 1.0); let fres = pow(1.0 - clamp(dot(n, vdir), 0.0, 1.0), 3.0); // Body diffuse → silver; fresnel edge → position-mixed neon // (the two-colour bulb effect lives here). body = base * (0.20 + 0.80 * lambert) + acc_mix * fres * 0.28; // Hi-band rim → **secondary** accent² (not the body's main) // so a snare flashes a contrasting hue against the body's // base accent. Quadratic in highf so light hats stay near // zero, only strong snares light the edge. body = body + accent2 * highf * highf * fres * fres * 0.40; } // Particle-dither: less hectic in quiet parts let dither_thresh = clamp(inten * 2.2 - 0.08, 0.0, 1.0); let pcell = hash21(uv * vec2(res_w, res_h) * 0.85 + vec2(frame * 0.013, frame * 0.029)); let pkeep = step(pcell, dither_thresh); col = body * inten * (0.50 + 0.55 * pkeep); // Core punch — uses the per-pixel tint so the bulb's deep core // glows different shades in different regions, not one hot dot. col = col + acc_mix * pow(inten, 8.0) * 0.20; // Onset spark — gated by flash² and uses accent2 (the snare // colour) so onsets actively introduce the contrast hue. col = col + accent2 * flash * flash * pow(inten, 3.0) * 0.25; } } // Sub-bass gravity-well: radial darkening of the outer void, gated by // sub-bass loudness². Quadratic so a sustained low hum reads heavy but // doesn't reach the threshold without a real 808. A faint warm tint // (wine, mixed from accent + base) bleeds into the dark ring so a kick // colours the periphery instead of just dimming it. let r2 = dot(ndc, ndc); let gw = lowf * lowf * smoothstep(0.0, 1.6, r2); col = col * (1.0 - 0.28 * gw); let warm = mix(base, accent * 0.6, 0.45); col = col + warm * gw * 0.05; // --- mid-band ink-in-water swirl: feedback sample comes from a small // rotation around centre + the glitch grid's per-cell shove. Pad swells // make ribbons drift; a snare burst makes a sparse subset of cells jump. // Both effects are sub-pixel-ish in magnitude so the trail doesn't tear. var fb_uv = uv + glitch_off; if (u.p2.z > 0.5) { var c2 = fb_uv - vec2(0.5); let cs = cos(swirl * 0.5); let sn = sin(swirl * 0.5); c2 = vec2(c2.x * cs - c2.y * sn, c2.x * sn + c2.y * cs); let zr = 1.0 + 0.004 * (midf - 0.3); c2 = c2 * zr; fb_uv = clamp(c2 + vec2(0.5), vec2(0.0), vec2(1.0)); } // Dense fine grain — sells the "millions of particles" texture. col = max(col + (hash21(uv * vec2(res_w, res_h) + vec2(frame * 1.1, frame * 1.7)) - 0.5) * grain_a, vec3(0.0)); // Phosphor feedback + datamosh. Stutter raises the decay floor briefly // so the previous frame survives a beat (reads as a dropped frame), but // capped well below 1.0 so the trail still drains — no runaway wash. // CA across the tap is small by default; only the active stutter window // lifts it. if (u.p2.z > 0.5) { let dec_base = clamp(1.0 - 3.5 * fade, 0.30, 0.85); let stutter_floor = mix(dec_base, 0.90, clamp(stut, 0.0, 1.0)); let decay = clamp(stutter_floor + 0.10 * beat, 0.30, 0.94); let ca = ca_px / max(res_w, 1.0); let off = (fb_uv - vec2(0.5)) * ca; let pr = textureSampleLevel(prev_tex, prev_smp, fb_uv + off, 0.0).r; let pg = textureSampleLevel(prev_tex, prev_smp, fb_uv, 0.0).g; let pb = textureSampleLevel(prev_tex, prev_smp, fb_uv - off, 0.0).b; col = max(col, vec3(pr, pg, pb) * decay); } // Cold void floor — silver/blue, very small so it cannot accumulate // through the feedback trail. A tonality-gated mix toward `accent2` // gives the void itself a faint partner-neon cast so the corners of the // frame aren't pure black. Quadratic loudness gating so the floor only // lifts on real energy, not analyser noise. let vig = max(1.0 - 0.85 * length(ndc), 0.0); let bgt_cold = mix(vec3(0.015, 0.020, 0.035), vec3(0.04, 0.05, 0.10), tonal); let bgt = mix(bgt_cold, accent2 * 0.08, 0.25 * tonal); col = col + bgt * vig * (0.005 + 0.010 * loud * loud); return vec4(min(col, vec3(1.0)), 1.0); }