Vignette
Purpose
A post-FX effect is almost always a mask computed in the signal layer, multiplied onto a sampled image in the color layer — vignette is the smallest, cleanest instance of that pattern.
Key insight
The canvas above is already showing a rendered scene. To darken the edges, the shader does two jobs per pixel: it gets the scene's color, and separately computes a number — a mask — that's bright in the middle and dark near the corners. The mask comes from length(uv - 0.5) fed through a smoothstep. Then the two are multiplied.
This "sample + mask + multiply" recipe is the template for vignette, bloom, film grain, dirty-lens — every screen-darkener you'll ever direct. Everything different between them is how the mask is computed.
Break it
Drag darkness to 1 and softness to 0. The image becomes a crisp black frame surrounding a visible center — the mask is now rendering as a shape you can point at. Teaches: a vignette is a soft-edged shape you multiply onto the image. Soften the edge and it reads as mood; harden it and it reads as a port-hole.
Now drop darkness back to 0 — the scene underneath is untouched. The mask was never destructive; you were only deciding how much of it to apply.
Direct Claude
uniform float u_darkness;
uniform float u_softness;
// The synthetic scene we're "sampling" — a sunset gradient + mountains + grid.
vec3 scene(vec2 uv) {
vec3 col = mix(vec3(0.18, 0.10, 0.28), vec3(0.95, 0.48, 0.32), uv.y);
col = mix(col, vec3(0.25, 0.60, 0.85), smoothstep(0.55, 0.98, uv.y));
float sun = 1.0 - smoothstep(0.09, 0.11, length(uv - vec2(0.5, 0.55)));
col = mix(col, vec3(1.0, 0.93, 0.68), sun);
float mntn = 0.40 + 0.05 * sin(uv.x * 18.0) + 0.08 * sin(uv.x * 5.0 + 2.0);
col = mix(col, vec3(0.10, 0.06, 0.20), step(uv.y, mntn) * step(uv.y, 0.5));
if (uv.y < 0.5) {
float yy = (0.5 - uv.y);
float hz = smoothstep(0.004, 0.0, abs(fract(pow(yy, 0.7) * 8.0 - u_time * 0.25) - 0.5));
float vl = smoothstep(0.004, 0.0, abs(fract((uv.x - 0.5) / max(yy, 0.02) * 4.0) - 0.5));
col = mix(col, vec3(0.95, 0.30, 0.75), max(hz, vl) * 0.9);
}
return col;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
// COLOR LAYER — get the scene.
vec3 col = scene(uv);
// SIGNAL LAYER — distance from center, through a smoothstep.
// Hard edge at softness=0; gentle falloff at softness=0.5.
float d = length(uv - vec2(0.5));
float mask = 1.0 - smoothstep(0.35, 0.35 + u_softness + 0.001, d);
// COLOR LAYER — blend the mask in. u_darkness controls how much.
col *= mix(1.0, mask, u_darkness);
gl_FragColor = vec4(col, 1.0);
}