Vignette & film grain
Darken the corners of the frame and sprinkle it with moving noise, the two marks a real camera leaves on its picture.
Cards
One live example to tweak, learn and copy.
How it works
Real lenses pass less light toward the corners of the frame than through the middle, so photos and films darken toward the edges. That falloff is the vignette. Film adds its own texture on top: the image is made of tiny light-sensitive crystals, randomly placed and different in every frame, which shows as a fine moving grain. Digital sensors have a cousin of it, noise, strongest in the shadows.
Both are cheap to fake in one post-processFull-screen passes run in order over the finished frame, each reading the image the previous one wrote. Bloom, tone mapping and color grading live here. Post-process chain Full-screen passes run in order over the finished frame, each reading the image the previous one wrote. Bloom, tone mapping and color grading live here.
- Vignette. Measure each pixel’s distance from the center, with the aspect ratio corrected so the shape stays
round, and pass it through a smoothstepA soft threshold. It is 0 below the first edge, 1 above the second, and follows an S-shaped curve with flat ends in between.Fundamental
Smoothstep
A soft threshold. It is 0 below the first edge, 1 above the second, and follows an S-shaped curve with flat ends in between.
t = clamp((x − e0) / (e1 − e0), 0, 1); s = t² (3 − 2t)Sourcesto get a soft ring that scales the color down. It frames the subject and hides the flat, digital look of evenly lit corners.- smoothstep (opens in a new tab)Reference
- Smoothstep (opens in a new tab)Reference
- Grain. A hashA function that scrambles its input into a value that looks random but is always the same for the same input. Shaders use it for per-pixel and per-cell randomness.Fundamental
Hash
A function that scrambles its input into a value that looks random but is always the same for the same input. Shaders use it for per-pixel and per-cell randomness.
h = fract(sin(dot(p, k)) × big)of the pixel’s position and the frame number gives a different random value per pixel per frame. Scaled small and added or multiplied into the color, it reads as film. It also breaks up bandingBanding is the stripes a smooth gradient breaks into when stored with too few levels. Dithering adds tiny per-pixel noise before rounding, so the stripes become fine grain.RenderingBanding & dithering
Banding is the stripes a smooth gradient breaks into when stored with too few levels. Dithering adds tiny per-pixel noise before rounding, so the stripes become fine grain.
Sourcesin smooth gradients, the same way dither does.
Unity and Unreal have both built in; Godot has neither. Writing them yourself makes sense there, on the web, for a look the built-in one doesn’t offer (a projector’s flicker, a tinted stock), or to control where in the chain they run: after tone mapping, so the grain doesn’t get compressed along with the highlights.
Further reading
- Random (opens in a new tab)Book
- Hash Functions for GPU Rendering (opens in a new tab)Paper
- step (opens in a new tab)Reference
- smoothstep (opens in a new tab)Reference
- Shaping functions (opens in a new tab)Book
- gl_FragCoord (opens in a new tab)Reference
- Uniforms (opens in a new tab)Book
- UV mapping (opens in a new tab)Reference
- Framebuffers (opens in a new tab)Article
- Bloom (opens in a new tab)Article
- Custom post-processing (opens in a new tab)Docs
- Vignetting (opens in a new tab)Reference
- Film grain (opens in a new tab)Reference
- MurmurHash3 (opens in a new tab)Reference
- Relative luminance (opens in a new tab)Reference
- length (opens in a new tab)Reference