// Every `stamp` seconds the moving parts of the picture are frozen in place as a translucent, // tinted ghost; each ghost fades over `life` seconds, so moving things leave a row of copies. // order 70 // slider stamp 0.2 3.0 0.5 // slider life 0.2 6.0 2.0 // slider hue 0.0 1.0 0.55 // hue 0..1 -> a fully saturated colour (0 red, 0.08 orange, 0.33 green, 0.66 blue) static float3 hueColor(float h) { return clamp(abs(fract(h + float3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0) - 1.0, 0.0, 1.0); } float4 effect(float2 uv, Inputs in) { // The frozen ghosts are one number per pixel (a tinted brightness), so they live in the // alpha channel of this effect's own output. The colour is only added when displaying, so // `in.prev.rgb` never feeds the live picture back into itself. float ghosts = in.prev.sample(in.smp, uv).a; float frame = in.dt; ghosts *= exp(-frame / max(in.p.life, 0.05)); float3 live = in.image.sample(in.smp, uv).rgb; float diff = length(in.camera.sample(in.smp, uv).rgb - in.last.sample(in.smp, uv).rgb); float moving = smoothstep(0.03, 0.30, diff); float fresh = dot(live, float3(0.299, 0.587, 0.114)) * moving; // A stamp is taken just after each `stamp`-second slot of in.time begins. The window is two // frames wide, so a stamp is not missed when the frame rate falls below the nominal one. float slot = max(in.p.stamp, 0.05); float begins = 1.0 - step(2.0 * frame / slot, fract(in.time / slot)); ghosts = max(ghosts, fresh * begins); float3 tint = hueColor(in.p.hue); return float4(live + tint * ghosts * 0.85, ghosts); }