// Glitch family: a four-colour press run with the plates out of register. Each // ink is screened at its own angle, and each one lands a little off, so the // image is fringed and the dots moiré against each other. // // Every other glitch scene here corrupts a signal — tearing it, freezing it, // blocking it up. This one is a printing fault rather than an electronic one: // the image is never damaged, it is simply separated and reassembled wrong. // Nothing else in the library is made of dots, and the dots are what carry it. export const halftoneMisprint = { name: 'Halftone Misprint', family: 'glitch', kind: 'fragment', // The paper's tooth is the point; take the track's grain in full. texture: 1.2, consumes: ['ink'], traits: ['camera', 'style'], params: { screen: { type: 'float', range: [18, 90], default: 42, uniform: 'u_screen', bias: 'density' }, slip: { type: 'float', range: [0, 0.06], default: 0.02, uniform: 'u_slip' }, angle: { type: 'float', range: [0, 1.6], default: 0.5, uniform: 'u_angle' }, ink: { type: 'float', range: [0.3, 1.5], default: 0.9, uniform: 'u_ink', slowAxis: true }, art: { type: 'float', range: [0.5, 4], default: 1.6, uniform: 'u_art', bias: 'density' }, press: { type: 'float', range: [0.02, 0.4], default: 0.09, uniform: 'u_press', bias: 'motion', rate: true }, palette: { type: 'palette', count: 5 }, }, reactive: { slip: { feature: 'flux', amount: 0.3, response: 'spike' }, ink: { feature: 'bandLow', amount: 0.25, response: 'smooth' }, }, shader: ` // The artwork being printed: a soft, slow field, deliberately simple. What the // scene is about is the separation, not the picture. float artwork(vec2 q, float t) { float a = fbm(q * u_art + vec2(t * 0.5, -t * 0.3), 4); float b = 0.5 + 0.5 * sin(q.x * u_art * 2.0 + a * 4.0 + t); return sat(a * 0.7 + b * 0.5); } // One screened separation: sample the artwork where THIS plate landed, then // threshold it against a rotated dot grid. The dot grows with ink coverage, // which is what a halftone is. float plate(vec2 q, float ang, vec2 slip, float t, float gain) { float density = sat(artwork(q + slip, t) * gain); vec2 r = rot(ang) * (q + slip); // Ruling is a count of dots ACROSS THE FRAME, not a size in pixels — which // is what keeps a 720p preview and a 4K export the same print rather than // the same dot pitch on a bigger sheet. vec2 cell = fract(r * u_screen * 0.5) - 0.5; // Dot edge hardness is the track's: a sharp track prints a crisp dot on // coated stock, a soft one lets the ink spread into the fibre. float soft = 0.04 + u_sigSoft * 0.45; float radius = sqrt(max(density, 0.0)) * 0.62; return smoothstep(radius, radius - soft, length(cell)); } vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_press + u_seed; p = sigCamera(p); // Registration error. It steps on the bar rather than drifting: a press // slips, settles, and slips again, and a continuously crawling offset reads // as a wobble rather than as a fault. float era = floor(u_barPhase * 4.0) + floor(t * 2.0) * 4.0; vec2 slipC = (hash22(vec2(era, 1.0)) - 0.5) * u_slip; vec2 slipM = (hash22(vec2(era, 2.0)) - 0.5) * u_slip; vec2 slipY = (hash22(vec2(era, 3.0)) - 0.5) * u_slip; // Classic screen angles: 15°, 75°, 0°, spread by the angle parameter so a // track can have them in near-register or wildly rosetted. float a1 = 0.26 * u_angle, a2 = 1.31 * u_angle, a3 = 0.0; float c = plate(p, a1, slipC, t, u_ink); float m = plate(p, a2, slipM, t, u_ink); float y = plate(p, a3, slipY, t, u_ink); // Subtractive-ish: each ink takes light out of the paper, in its own hue. vec3 col = pal(1) * 0.85; col -= pal(2) * c * 0.5; col -= pal(3) * m * 0.5; col -= pal(4) * y * 0.45; // The black plate, printed last and in register, which is what stops the // whole thing turning to mud. float k = plate(p, 0.65 * u_angle, vec2(0.0), t, u_ink * 0.7); col = mix(col, pal(0) * 0.12, k * 0.55); // Ink edges take the track's line weight — a hard press or a soft one. col += pal(0) * sigEdge(0.5 - c) * (0.05 + u_sigLine * 0.35); col = max(col, vec3(0.0)); return vec4(inkValue(col), 1.0); } `, }; export default halftoneMisprint;