[ CC0 shader vibe commons · everything here is public domain ]
experimental backendWebGL2 · not opted in
Enable WebGPU controls in settings. WebGL2 remains unchanged.
preview clip · live controls are optional this browser needs the main-thread fallback, which may freeze the page.
params (28)
1.00
0.19
0.70
0.30
0.21
0.55
9.00
0.55
0.55
1.00
0.85
0.55
0.55
240.00
0.45
3.00
0.50
0.30
0.35
0.35
0.35
0.45
0.06
0.35
0.55
1.00
0.30
1.35
.fs source · isfnot running
/*{"DESCRIPTION":"The bouncing DVD logo, on a tube. The motion is closed form - nothing is integrated and no state is kept between frames, because a bounce inside a box is just a triangle wave: bounce(t,r) = |mod(t,4r) - 2r| - r, which ping-pongs across [-r,r] with period 4r. Each half period is exactly one wall hit, so the number of bounces so far is floor(t/2r), and that counter summed over both axes is what picks the colour. A corner is simply both axes arriving at an extreme in the same instant, and when they do the logo's own phosphor bloom swells and reaches further for the fraction of a second it is there - a local reward rather than a full-screen flash, deliberately, since a screen-wide strobe is a photosensitivity hazard and would white out the one thing worth watching - but that is a congruence rather than luck. Writing u = t/2r, x reaches a wall when u = -startX/2 (mod 1) and y when ratio*u = -startY/2 (mod 1), and those are simultaneously satisfiable only if startY = ratio*startX. The defaults satisfy it, so this really does land the corner, exactly, every 38 seconds; nudge either start off that relation and it becomes the thing that never quite happens, which is the more authentic screensaver and the less rewarding shader. Being closed form buys the thing that actually sells it as a CRT: phosphor persistence. Because position is a function of time rather than a running total, the shader can ask where the logo WAS a few milliseconds ago and draw it again, fainter, carrying the colour it had then - so a trail falls out of the maths instead of needing a feedback buffer, and just after a bounce it still shows the previous colour fading out, which is what a real tube does. The logo is signed distance fields rather than a texture: each D is a flat-sided stem unioned with the right half of an ellipse and then hollowed by the same function at a smaller radius, which gives it a D-shaped counter and an even stroke weight all the way round; the V is one stroke mirrored by folding x, with its apex dropping toward the disc; the disc is a wide flat ellipse with its hole punched out; and the whole wordmark is sheared to give it the italic lean. Everything after that is the television. The picture is bent onto a curved glass face first, so the logo, the scanlines and the grille all ride on the bend rather than being pasted flat over it - which is the difference between a CRT and a filter. Then it is scanned in lines, split into an aperture grille of three phosphor stripes, glowed by an exponential in the distance field, misconverged so the three guns do not quite land together and the error grows toward the edges, rolled by a mains-hum band, dusted with static, dimmed at the corners, and finally cropped by the tube's rounded bezel with a glare sitting on the glass in front of it. Twenty-eight controls: speed, logo size, the velocity ratio, expressed relative to the play area's own aspect so that it works as a corner dial - at 1.0 the axes reach their walls in step and the logo runs corner to corner forever, at any p/q it lands a corner every q bounces of x, and at an irrational value it never corners at all, where it sets off from on each axis, phosphor persistence with the number of echoes and their spacing, phosphor bleed, the corner bloom, colour saturation, glass curvature, scanline depth and count, aperture grille strength and pitch, gun misconvergence, interlacing that draws the odd lines from half a frame earlier - literal rather than faked, since the motion is closed form - line-to-line sync jitter, a head-switching tear that drifts along the bottom, a multipath ghost of the signal arriving twice, composite dot crawl keyed to the distance field so it only appears where there is an edge, static, mains flicker, vignette, the bezel crop, glass glare and overall brightness.","CREDIT":"CC0","CATEGORIES":["generator","crt","retro","screensaver","dvd","sdf","television","2d"],"INPUTS":[{"NAME":"uSpeed","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":4.0},{"NAME":"uSize","TYPE":"float","DEFAULT":0.19,"MIN":0.04,"MAX":0.45},{"NAME":"uRatio","TYPE":"float","DEFAULT":0.7,"MIN":0.15,"MAX":2.0},{"NAME":"uStartX","TYPE":"float","DEFAULT":0.3,"MIN":0.0,"MAX":4.0},{"NAME":"uStartY","TYPE":"float","DEFAULT":0.21,"MIN":0.0,"MAX":4.0},{"NAME":"uTrail","TYPE":"float","DEFAULT":0.55,"MIN":0.0,"MAX":1.0},{"NAME":"uTrailStep","TYPE":"float","DEFAULT":9.0,"MIN":1.0,"MAX":16.0},{"NAME":"uTrailGap","TYPE":"float","DEFAULT":0.55,"MIN":0.2,"MAX":4.0},{"NAME":"uGlow","TYPE":"float","DEFAULT":0.55,"MIN":0.0,"MAX":2.0},{"NAME":"uCorner","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":1.0},{"NAME":"uSat","TYPE":"float","DEFAULT":0.85,"MIN":0.0,"MAX":1.0},{"NAME":"uCurve","TYPE":"float","DEFAULT":0.55,"MIN":0.0,"MAX":1.5},{"NAME":"uScan","TYPE":"float","DEFAULT":0.55,"MIN":0.0,"MAX":1.0},{"NAME":"uScanCount","TYPE":"float","DEFAULT":240.0,"MIN":40.0,"MAX":700.0},{"NAME":"uMask","TYPE":"float","DEFAULT":0.45,"MIN":0.0,"MAX":1.0},{"NAME":"uMaskScale","TYPE":"float","DEFAULT":3.0,"MIN":1.0,"MAX":8.0},{"NAME":"uInterlace","TYPE":"float","DEFAULT":0.5,"MIN":0.0,"MAX":1.0},{"NAME":"uJitter","TYPE":"float","DEFAULT":0.3,"MIN":0.0,"MAX":2.0},{"NAME":"uTracking","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":2.0},{"NAME":"uGhost","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":1.0},{"NAME":"uCrawl","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":1.0},{"NAME":"uAberr","TYPE":"float","DEFAULT":0.45,"MIN":0.0,"MAX":2.0},{"NAME":"uNoise","TYPE":"float","DEFAULT":0.06,"MIN":0.0,"MAX":1.0},{"NAME":"uFlicker","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":1.0},{"NAME":"uVignette","TYPE":"float","DEFAULT":0.55,"MIN":0.0,"MAX":1.0},{"NAME":"uBezel","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":1.0},{"NAME":"uGlare","TYPE":"float","DEFAULT":0.3,"MIN":0.0,"MAX":1.0},{"NAME":"uBright","TYPE":"float","DEFAULT":1.35,"MIN":0.0,"MAX":2.0}]}*/// DVD - ISF port of the Shadertoy/WebGL2 version// (GLSL ES 1.00: gl_FragColor, TIME/RENDERSIZE).//// The bounce is a triangle wave, so position is a pure function of time and// the phosphor trail is just the same function evaluated a few milliseconds// into the past. The logo is signed distance fields; the tube is everything// after that.#define PI 3.14159265359#define TAU 6.28318530718// Ceiling on the persistence loop. It is constant-bound and gets inlined once// per colour gun when misconvergence is on, so the early `break` is what stops// ANGLE unrolling three copies of the whole thing flat.#define MAXTRAIL 16// Half-width of the logo in units where its half-height is 1.#define LOGO_HW 1.75// ------------------------------------------------------------// small primitives// ------------------------------------------------------------floathash12(vec2 p){
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
returnfract(p.x * p.y);
}
floatsdBox(vec2 p, vec2 b){
vec2 d = abs(p) - b;
returnmin(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}
// The usual cheap ellipse: exact enough for a filled glyph, and it degenerates// gracefully at the centre where the gradient vanishes.floatsdEllipse(vec2 p, vec2 r){
float k1 = length(p / r);
float k2 = length(p / (r * r));
return k1 * (k1 - 1.0) / max(k2, 1e-6);
}
floatsdSeg(vec2 p, vec2 a, vec2 b){
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-9), 0.0, 1.0);
returnlength(pa - ba * h);
}
vec3hsv2rgb(vec3 c){
vec3 k = abs(fract(c.xxx + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0);
return c.z * mix(vec3(1.0), clamp(k - 1.0, 0.0, 1.0), c.y);
}
// ------------------------------------------------------------// the glyphs// ------------------------------------------------------------// A "D" silhouette: a flat-sided stem unioned with the right half of an// ellipse. Hollowing it with the SAME function at a smaller radius gives a// counter that is itself D-shaped, which is what a real D has — and it keeps// the stroke weight even all the way round, since every wall is the difference// of the two radii.floatdOuter(vec2 p, vec2 s){
float stem = sdBox(p - vec2(-s.x * 0.5, 0.0), vec2(s.x * 0.5, s.y));
float bowl = max(sdEllipse(p, s), -p.x);
returnmin(stem, bowl);
}
floatglyphD(vec2 p, vec2 s, float t){
returnmax(dOuter(p, s), -dOuter(p, s - vec2(t, t)));
}
// A "V": one stroke, mirrored by folding x. The fold is why this is a single// segment rather than two.floatglyphV(vec2 p, float hw, float top, float apex, float t){
p.x = abs(p.x);
float d = sdSeg(p, vec2(hw, top), vec2(0.0, apex)) - t * 0.5;
returnmax(d, p.y - top); // square off the tops
}
floatglyphDisc(vec2 p, vec2 r, vec2 rh){
returnmax(sdEllipse(p, r), -sdEllipse(p, rh));
}
// The whole mark, in units where its half-height is 1.floatlogoSDF(vec2 q){
vec2 s = vec2(q.x - q.y * 0.26, q.y); // italic leanfloat th = 0.155; // stroke weightfloat d = glyphD(s - vec2(-1.02, 0.50), vec2(0.46, 0.47), th);
d = min(d, glyphD(s - vec2( 1.02, 0.50), vec2(0.46, 0.47), th));
d = min(d, glyphV(s - vec2(0.0, 0.50), 0.60, 0.47, -0.72, th));
d = min(d, glyphDisc(q - vec2(0.0, -0.62), vec2(1.62, 0.33), vec2(0.30, 0.075)));
return d;
}
// ------------------------------------------------------------// the bounce, in closed form// ------------------------------------------------------------// Ping-pong across [-r, r]. Period 4r; each half period is one wall hit.floatbounce(float t, float r){
returnabs(mod(t, 4.0 * r) - 2.0 * r) - r;
}
// How many walls have been struck on this axis by time t.floatbounceCount(float t, float r){
returnfloor(t / (2.0 * r));
}
// The y speed is expressed RELATIVE to the play area's own aspect. That is what// turns `ratio` into a corner dial: at 1.0 the two axes reach their walls in// step and the logo runs corner to corner forever, and at any p/q it lands a// corner every q bounces of x. Irrational values never corner at all — which is// the honest default for a screensaver, but a cruel one for a shader nobody// watches for twenty minutes.floatvyOf(vec2 rng){ return uRatio * rng.y / rng.x; }
vec2logoPos(float t, vec2 rng){
returnvec2(bounce(t + uStartX * rng.x, rng.x),
bounce(t * vyOf(rng) + uStartY * rng.y, rng.y));
}
// Colour is a pure function of the number of bounces so far, so the trail// carries the colour the logo HAD at that moment, not the one it has now.vec3logoColour(float t, vec2 rng){
float bi = bounceCount(t + uStartX * rng.x, rng.x)
+ bounceCount(t * vyOf(rng) + uStartY * rng.y, rng.y);
float h = fract(sin(bi * 78.233 + 12.9898) * 43758.5453);
returnhsv2rgb(vec3(h, uSat, 1.0));
}
// How close the logo is to a corner right now: both axes at an extreme in the// same instant. 1 exactly on a corner, 0 anywhere else.floatcornerness(float t, vec2 rng){
float ex = abs(bounce(t + uStartX * rng.x, rng.x)) / rng.x;
float ey = abs(bounce(t * vyOf(rng) + uStartY * rng.y, rng.y)) / rng.y;
returnsmoothstep(0.978, 1.0, ex) * smoothstep(0.978, 1.0, ey);
}
// ------------------------------------------------------------// the logo, plus its afterglow// ------------------------------------------------------------// Returns rgb over black in .rgb and the coverage in .a.vec4logoLayer(vec2 p, float t, vec2 rng, float sc, float pxw){
vec4 acc = vec4(0.0);
int NT = int(uTrailStep + 0.5);
float w = pxw / sc * 0.8;
for (int k = 0; k < MAXTRAIL; k++){
if (k >= NT) break;
// Oldest first, so each newer echo composites OVER the ones behind it.int e = NT - 1 - k;
float fe = float(e);
float tk = t - fe * uTrailGap * 0.02;
float d = logoSDF((p - logoPos(tk, rng)) / sc);
float m = smoothstep(w, -w, d);
float a = m * ((e == 0) ? 1.0 : uTrail * exp(-fe * 0.55));
acc.rgb = mix(acc.rgb, logoColour(tk, rng), a);
acc.a = max(acc.a, a);
}
return acc;
}
// ------------------------------------------------------------voidmain(){
vec2 fragCoord = gl_FragCoord.xy;
vec2 R = RENDERSIZE.xy;
float asp = R.x / R.y;
float t = TIME * uSpeed * 0.35;
// Normalised so y runs -1..1 and the corners of the tube sit at |n| = 1.vec2 uv0 = (fragCoord - 0.5 * R) / (0.5 * R.y);
vec2 n = uv0 / vec2(asp, 1.0);
// The picture is painted on a curved glass face, so bend the sampling// outward before anything is drawn. Everything downstream — the logo, the// scanlines, the grille — rides on the bend rather than being pasted flat// over it, which is the difference between a CRT and a filter.float r2 = dot(n, n);
vec2 nb = n * (1.0 + uCurve * 0.22 * r2);
vec2 p = nb * vec2(asp, 1.0);
// ---- the beam is not quite where it should be ----------------------// A line is drawn by a beam whose horizontal start is only as stable as the// sync separator. Everything here moves the SAMPLE POINT — that is, moves// the beam — rather than smearing a picture that was drawn correctly.float line = floor((nb.y * 0.5 + 0.5) * uScanCount);
float frame = floor(TIME * 60.0);
float jit = (hash12(vec2(line, frame)) - 0.5) * 0.010
+ sin(nb.y * 31.0 + TIME * 5.3) * 0.0016;
p.x += jit * uJitter;
// Head switching: the few lines at the bottom where the drum changes heads// and the line start is simply wrong. It drifts, because the tape does.float by = -0.885 + 0.035 * sin(TIME * 0.61);
float band = smoothstep(0.055, 0.0, abs(nb.y - by));
p.x += band * (hash12(vec2(line, frame * 0.37)) - 0.5) * 0.16 * uTracking;
// Play area: the logo bounces on the tube face, so the travel is measured// in the same bent space the picture lives in.float sc = max(uSize, 0.02);
vec2 rng = max(vec2(asp - LOGO_HW * sc, 1.0 - sc), vec2(0.05));
float pxw = 2.0 / R.y;
float corner = uCorner * cornerness(t, rng);
// ---- interlace ------------------------------------------------------// Odd and even lines are separate FIELDS, captured half a frame apart. The// closed-form motion makes that literal instead of faked: the odd lines// simply ask where the logo was a moment earlier. On motion the vertical// edges serrate, which is the comb everybody remembers seeing.float tf = t - mod(line, 2.0) * uInterlace * 0.006;
// Misconvergence: the three guns do not quite land on the same spot, and// the error grows toward the edges of the tube. Evaluating the layer once// per gun is the honest way to do it with no buffer to sample from — the// branch is on a uniform, so it stays coherent across the warp.vec4 L;
if (uAberr > 0.001){
vec2 e = vec2(uAberr * 0.006 * (0.25 + r2), 0.0);
vec4 Lr = logoLayer(p + e, tf, rng, sc, pxw);
vec4 Lg = logoLayer(p, tf, rng, sc, pxw);
vec4 Lb = logoLayer(p - e, tf, rng, sc, pxw);
L = vec4(Lr.r, Lg.g, Lb.b, max(Lg.a, max(Lr.a, Lb.a)));
} else {
L = logoLayer(p, tf, rng, sc, pxw);
}
vec3 col = L.rgb;
// Phosphor bleed. The distance field is right there, so the halo is an// exponential in it rather than a blur of something already drawn.//// The corner celebration rides on this same halo rather than on the frame.// It used to add a full-screen flash, which was wrong twice over: a// screen-wide strobe on a gallery that autoplays on hover is a// photosensitivity hazard, and it whited out the one thing actually worth// watching — the logo landing the corner. So the reward is now LOCAL: for// the ~40 ms it is in the corner the logo's own bloom brightens and reaches// further, and the rest of the tube is left alone.float bloom = uGlow * 0.55 + corner * 0.85;
if (bloom > 0.001){
float dn = logoSDF((p - logoPos(t, rng)) / sc);
col += logoColour(t, rng) * exp(-max(dn, 0.0) * mix(9.0, 3.2, corner)) * bloom;
}
// ---- multipath ghost -------------------------------------------------// The same signal arriving late off a second path, drawn faintly beside// itself. It is a second evaluation of the layer because there is no buffer// to offset — affordable only because the whole shader is cheap.if (uGhost > 0.001){
col += logoLayer(p - vec2(0.055, 0.0), tf, rng, sc, pxw).rgb * uGhost * 0.30;
}
// ---- dot crawl -------------------------------------------------------// Composite carries chroma on a subcarrier the decoder cannot fully// separate from luma, so colour edges grow a fine checkerboard that creeps// along them. Keyed off the distance field, so it can only appear where// there actually IS an edge.if (uCrawl > 0.001){
float de = abs(logoSDF((p - logoPos(t, rng)) / sc));
float ck = sin((fragCoord.x + fragCoord.y * 2.0) * 1.05 - TIME * 42.0);
col += logoColour(t, rng) * ck * exp(-de * 14.0) * uCrawl * 0.22;
}
// ---- the television ------------------------------------------------// Scanlines, in a fixed count so the look holds at any resolution.float sl = sin(nb.y * uScanCount * PI * 0.5);
col *= mix(1.0, 0.45 + 0.55 * sl * sl, uScan);
// Aperture grille: the three phosphor stripes, at a pitch in real pixels.float gx = mod(floor(fragCoord.x / max(uMaskScale, 1.0)), 3.0);
vec3 grille = vec3(gx == 0.0 ? 1.0 : 0.55,
gx == 1.0 ? 1.0 : 0.55,
gx == 2.0 ? 1.0 : 0.55);
col *= mix(vec3(1.0), grille, uMask);
// Mains hum: a slow bright band rolling up the picture, plus a little// flicker on the whole frame.float roll = sin((nb.y * 0.5 - TIME * 0.13) * TAU);
col *= 1.0 + uFlicker * (0.05 * roll + 0.02 * sin(TIME * 47.0));
// Static.
col += (hash12(fragCoord + fract(TIME) * 311.0) - 0.5) * uNoise * 0.35;
// Corner fall-off on the tube.float vig = 1.0 - uVignette * 0.85 * smoothstep(0.35, 1.45, r2);
col *= vig;
col *= uBright;
// Glare on the glass — a soft off-centre reflection that does not move// with the picture, because it is in front of it.
col += vec3(0.55, 0.62, 0.75) * uGlare * 0.10
* exp(-8.0 * dot(nb - vec2(-0.45, 0.42), nb - vec2(-0.45, 0.42)));
// The tube's crop: a rounded rectangle, with a thin lit rim just inside// the edge where the glass turns away.float sd = sdBox(nb, vec2(0.985, 0.985) - 0.16) - 0.16;
float ins = smoothstep(0.006, -0.006, sd);
float rim = smoothstep(0.045, 0.0, abs(sd + 0.012));
col = mix(col, col * 0.0, (1.0 - ins) * uBezel);
col += vec3(0.10, 0.11, 0.13) * rim * ins * uBezel;
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}
The bouncing DVD logo, on a tube. The motion is closed form - nothing is integrated and no state is kept between frames, because a bounce inside a box is just a triangle wave: bounce(t,r) = |mod(t,4r) - 2r| - r, which ping-pongs across [-r,r] with period 4r. Each half period is exactly one wall hit, so the number of bounces so far is floor(t/2r), and that counter summed over both axes is what picks the colour. A corner is simply both axes arriving at an extreme in the same instant, and when they do the logo's own phosphor bloom swells and reaches further for the fraction of a second it is there - a local reward rather than a full-screen flash, deliberately, since a screen-wide strobe is a photosensitivity hazard and would white out the one thing worth watching - but that is a congruence rather than luck. Writing u = t/2r, x reaches a wall when u = -startX/2 (mod 1) and y when ratio*u = -startY/2 (mod 1), and those are simultaneously satisfiable only if startY = ratio*startX. The defaults satisfy it, so this really does land the corner, exactly, every 38 seconds; nudge either start off that relation and it becomes the thing that never quite happens, which is the more authentic screensaver and the less rewarding shader. Being closed form buys the thing that actually sells it as a CRT: phosphor persistence. Because position is a function of time rather than a running total, the shader can ask where the logo WAS a few milliseconds ago and draw it again, fainter, carrying the colour it had then - so a trail falls out of the maths instead of needing a feedback buffer, and just after a bounce it still shows the previous colour fading out, which is what a real tube does. The logo is signed distance fields rather than a texture: each D is a flat-sided stem unioned with the right half of an ellipse and then hollowed by the same function at a smaller radius, which gives it a D-shaped counter and an even stroke weight all the way round; the V is one stroke mirrored by folding x, with its apex dropping toward the disc; the disc is a wide flat ellipse with its hole punched out; and the whole wordmark is sheared to give it the italic lean. Everything after that is the television. The picture is bent onto a curved glass face first, so the logo, the scanlines and the grille all ride on the bend rather than being pasted flat over it - which is the difference between a CRT and a filter. Then it is scanned in lines, split into an aperture grille of three phosphor stripes, glowed by an exponential in the distance field, misconverged so the three guns do not quite land together and the error grows toward the edges, rolled by a mains-hum band, dusted with static, dimmed at the corners, and finally cropped by the tube's rounded bezel with a glare sitting on the glass in front of it. Twenty-eight controls: speed, logo size, the velocity ratio, expressed relative to the play area's own aspect so that it works as a corner dial - at 1.0 the axes reach their walls in step and the logo runs corner to corner forever, at any p/q it lands a corner every q bounces of x, and at an irrational value it never corners at all, where it sets off from on each axis, phosphor persistence with the number of echoes and their spacing, phosphor bleed, the corner bloom, colour saturation, glass curvature, scanline depth and count, aperture grille strength and pitch, gun misconvergence, interlacing that draws the odd lines from half a frame earlier - literal rather than faked, since the motion is closed form - line-to-line sync jitter, a head-switching tear that drifts along the bottom, a multipath ghost of the signal arriving twice, composite dot crawl keyed to the distance field so it only appears where there is an edge, static, mains flicker, vignette, the bezel crop, glass glare and overall brightness.
by ksinwitnessed_generationmade with ClaudeCC0-1.0credit: CC0active·created 2026-08-31 06:33Z127 views0 forks28 paramsClaude-Opus-5
export clip · render a video in your browser
Renders to video in your browser (no upload), stepping the real renderer frame by frame so
multipass + feedback come out right. motion auto-animates the shader's own knobs — each export
rolls a fresh take (🎲 re-roll, 🔒 to pin one you like). GIF is slower with a tighter length cap;
MP4/WebM are faster. Some devices record in real time (noted below if so) — keep the tab visible.
caps: ≤1920 per side, ≤60fps, ≤18000 frames (GIF ≤300)