vidar.games

Temporal anti-aliasing (TAA)

2026-11-08 ยท Discuss on X

Thin wires and a checker floor shimmer at 1 sample per pixel. TAA shifts the camera by under a pixel every frame and blends each frame with the last.

The problem

A pixel takes one sample. Anything thinner than a pixel, like a power line or a lattice brace, pops in and out as the camera moves. MSAA takes extra samples on triangle edges, but a pattern drawn inside a shader (the checker floor here) still shimmers. Supersampling fixes both, at several times the cost of every frame.

How it works

The source (a Unity URP renderer feature) spreads the extra samples over time:

  1. Jitter: each frame the projection is offset by a Halton (2,3) point, 8 points in a loop, up to half a pixel each way.
  2. Reproject: read the depth, rebuild the pixel's view position with the un-jittered inverse projection, and project it with last frame's view-projection. That gives where this pixel was in the history.
  3. Clamp: take the min and max colour of the 3x3 neighbourhood in the current frame and clamp the history colour into that box.
  4. Blend: 95% history, 5% new frame. The history weight drops with camera motion: exp(-100 x screen distance moved).
  5. Ping-pong two history buffers.

What I hit

The reprojection uses depth and camera motion only; there are no per-object motion vectors. A box moving on its own reads the wrong history. With the clamp off it leaves a long orange trail and is barely visible itself. With the clamp on the trail is mostly gone: the old colour falls outside the 3x3 range and gets pulled back.

The history starts empty, so the first frames would fade in from black. I seed a new history from the current frame.

My build

three.js (r187), WebGL2. Each panel is the same 640 x 1080 slice of one 1920 x 1080 view, so all three show the same view at the same scale. MSAA is a 4-sample render target. TAA history is two half-float targets per panel.

Scene: five lattice pylons 26 m tall with 5 cm braces, four power lines 5 cm thick, a 70-slat fence, and a checker floor with no texture filtering.

Trade-offs

When to use it

Scenes full of thin geometry, foliage or noisy shading where MSAA leaves shimmer. Add motion vectors before relying on it for fast-moving objects.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique source: URPTemporalAA by CMDRSpirit (Pascal Zwick), https://github.com/CMDRSpirit/URPTemporalAA, MIT license, commit 86f4d28bc5ee8115bff87ee61afe398a6b03f61a. Ported to three.js and GLSL from its TAA shader and renderer feature: the Halton (2,3) projection jitter (8 points, half a pixel), depth-based reprojection with the previous frame's un-jittered view-projection, the 3x3 neighbourhood min/max clamp, the history blend (0.95, reduced by camera motion) and the ping-pong history. The Halton points and default settings come from the source. No files were copied unchanged.

Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, branch dev at commit fa01dd6ad164c48554f1bb9456f6500db052ec4f. Used for rendering, render targets and the 4-sample MSAA target. It is not the source of the TAA technique.

My own code: the side-by-side panels, the clamp on/off switch, seeding an empty history from the current frame, the procedural pylons, power lines, fence and checker floor, the moving box, zoom boxes, jitter diagram, labels and video. No external models or assets. No IA model.