Fixing DirectX TAA Ghosting Using History Rejection
Temporal Anti-Aliasing (TAA) is a vital technique in modern DirectX rendering pipelines for eliminating jagged edges and specular shimmer, but it frequently introduces distracting visual trails known as ghosting. This article examines the core causes of ghosting artifacts in DirectX-based TAA implementations—including inaccurate motion vectors, disocclusion, and sub-pixel geometric changes—and details how implementing robust history rejection mechanisms effectively eliminates these trails while preserving image stability.
The Causes of Ghosting Artifacts in TAA
TAA achieves smooth edges by reprojecting pixels from previous frames into the current frame using 2D screen-space motion vectors (velocity buffers). Ghosting occurs when the temporal accumulator erroneously blends outdated visual information from the history buffer into pixels where the scene's actual content has fundamentally changed.
In a DirectX pipeline, several specific factors trigger this failure:
- Disocclusion: When a moving object uncovers a previously hidden background, the newly revealed pixels have no valid historical counterpart from the prior frame. If the shader attempts to sample the history buffer at the reprojected location, it fetches data belonging to the foreground object that was previously there, leaving a smear of the moving object behind it.
- Incomplete or Inaccurate Velocity Buffers: Motion
vectors are typically generated using vertex transformations between the
current and previous frames. However, many visual elements fail to write
accurate velocity:
- Alpha-tested and transparent surfaces (foliage, glass, water) often do not write to the velocity or depth buffers.
- Procedural animations (wind shaders, vertex deformation shaders) may skip previous-position calculations in the vertex shader to save performance.
- Particle systems and post-processing effects rarely track per-pixel temporal motion.
- Depth Discontinuities and Sub-Pixel Details: Extremely thin geometry, such as wires or distant foliage, can move across pixels faster than the sampling rate. If the velocity buffer samples an incorrect depth at a geometry edge (e.g., background velocity instead of foreground edge velocity), stale color data is smeared across the silhouette.
How History Rejection Resolves Ghosting
History rejection detects when the historical sample no longer represents the current surface and dynamically discards or down-weights the accumulated temporal data. Instead of blindly blending the history buffer via a simple lerp operation, DirectX compute or pixel shaders execute several validation steps:
1. Color Clamping and Clipping
The most common and effective history rejection technique is color-space bounding. The shader samples a small neighborhood (typically a 3x3 pixel kernel) around the current pixel to determine the local color distribution.
- AABB Clamping: The shader computes the minimum and maximum color values within the 3x3 footprint in a perceptual color space, such as YCoCg. The reprojected history color is then clamped to this Axis-Aligned Bounding Box (AABB). If an object moves away, the stale history color will fall far outside the current 3x3 color bounds and be clamped immediately to the current neighborhood's colors, erasing the ghost trail.
- Variance Clipping: Rather than using strict minimum and maximum values, variance clipping calculates the mean and standard deviation (\(\sigma\)) of the 3x3 neighborhood to define an oriented bounding box. History colors are clipped along the segment connecting the history sample to the current color. This significantly reduces ghosting while allowing for softer, more stable anti-aliasing on static geometry.
2. Depth-Based Disocclusion Testing
DirectX implementations can compare the current pixel's linear depth against the depth recorded in the history buffer at the reprojected coordinate. If the reprojected depth is substantially closer to the camera than the current depth, the foreground object has moved away, exposing the background. The shader triggers a rejection flag, instantly dropping the history weight to zero and forcing the pixel to rely entirely on the current frame's rasterized data.
3. Velocity Weighting and Dilation
To prevent thin edges from inheriting background motion vectors, implementations use velocity dilation. A 3x3 search finds the motion vector associated with the closest depth sample to the camera and applies it to the entire local neighborhood. Ensuring the motion vector accurately follows foreground contours prevents the history sampler from pulling pixels from unrelated scene surfaces.