motionlab/effects/glitch-blocks.metal
0xrsydn 169c5f9b63 Add in.dt, slider warnings in --check, and two agent-written effects
Spec fixes come from a test where pi + deepseek-v4.1-flash wrote effects
from EFFECTS.md alone: glitch-blocks and ghost-stamps both compiled on the
first try. The agent's feedback led to in.dt (frame time), documented
limits of in.prev as memory, and a --check warning for ignored slider lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:50:31 +07:00

57 lines
2.2 KiB
Metal

// Movement breaks the picture into horizontal bands that jump sideways and flash
// with shifted colours. Still frames pass through unchanged.
// order 70
// slider band 2.0 200.0 48.0
// slider intensity 0.0 2.0 1.0
// slider sensitivity 1.0 60.0 24.0
// slider shift 0.0 0.3 0.08
// slider split 0.0 0.06 0.012
// slider flash 0.0 1.0 0.7
// slider rate 1.0 30.0 12.0
// A repeatable pseudo random number in 0..1 from a 2D seed.
static float hash21(float2 p) {
p = fract(p * float2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
// How much the picture moved near this spot, in 0..1.
static float motionAt(float2 uv, Inputs in) {
float2 flow = in.flow.sample(in.smp, uv).xy;
float3 now = in.camera.sample(in.smp, uv).rgb;
float3 before = in.last.sample(in.smp, uv).rgb;
float moved = length(now - before);
return clamp(length(flow) * in.p.sensitivity + moved * in.p.sensitivity * 0.05, 0.0, 1.0);
}
float4 effect(float2 uv, Inputs in) {
float bandPixels = max(in.p.band, 1.0);
float bandHeight = bandPixels / in.resolution.y;
float band = floor(uv.y / bandHeight);
float centre = (band + 0.5) * bandHeight;
// The whole band reacts to the motion measured at its middle.
float strength = clamp(motionAt(float2(uv.x, centre), in) * in.p.intensity, 0.0, 1.0);
// Each band gets fresh random values a few times per second.
float tick = floor(in.time * in.p.rate);
float r1 = hash21(float2(band, tick));
float r2 = hash21(float2(band + 71.3, tick));
float r3 = hash21(float2(band + 13.7, tick + 5.0));
float slide = (r1 * 2.0 - 1.0) * in.p.shift * strength;
float split = (r2 * 2.0 - 1.0) * in.p.split * strength;
float2 base = float2(uv.x + slide, uv.y);
float3 col = in.image.sample(in.smp, base).rgb;
col.r = in.image.sample(in.smp, float2(base.x + split, base.y)).r;
col.b = in.image.sample(in.smp, float2(base.x - split, base.y)).b;
// Flash with a shifted copy of the channels.
float3 other = mix(col.gbr, col.brg, step(0.5, r3));
col = mix(col, other, in.p.flash * strength);
col *= 1.0 + (r1 - 0.5) * in.p.flash * strength;
return float4(col, 1.0);
}