Anti-Aliasing — MSAA, FXAA, TAA and Supersampling
In this tutorial, you'll learn about Anti. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Anti-aliasing eliminates jagged edges (aliasing artifacts) in rendered images by smoothing the transition between pixel colors, using techniques ranging from hardware multisampling to temporal accumulation.
What You'll Learn & Why It Matters
In this tutorial, you will learn the main anti-aliasing techniques — SSAA (supersampling), MSAA (multisample), FXAA (post-Process), and TAA (temporal). You will understand the quality-performance trade-off of each approach.
Real-world use: Every game and 3D application uses some form of anti-aliasing. The security camera feeds in Durga Antivirus Pro use FXAA for smooth edges at minimal performance cost.
Prerequisites
- Rendering Pipeline (previous)
- Post-Processing (previous)
- Understanding of pixel sampling
Learning Path
flowchart LR A[Post-Processing] --> B[Anti-Aliasing] B --> C[Compute Shaders] B --> D[GPU Architecture] B --> E[Real-Time GI] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
What Causes Aliasing?
Aliasing occurs because a pixel samples only one color at its center. When a triangle edge passes through a pixel, the pixel is either fully inside or fully outside, creating a staircase pattern.
import numpy as np
def render_without_aa(width, height, triangle):
"""Render a triangle without anti-aliasing."""
image = np.zeros((height, width))
for y in range(height):
for x in range(width):
cx, cy = x + 0.5, y + 0.5
if point_in_triangle(cx, cy, triangle):
image[y, x] = 1.0
return image
flowchart TD A[Scene Geometry] --> B[Pixel Grid] B --> C[Single Sample Per Pixel] C --> D[Jagged Edges (Aliased)] B --> E[Multiple Samples Per Pixel] E --> F[Smooth Edges (Anti-Aliased)]
SSAA (Supersampling Anti-Aliasing)
SSAA renders the scene at a higher resolution (e.g., 2x or 4x) and downsamples to the display resolution. It produces the highest quality but costs the most performance.
// SSAA resolve shader - averages 4 samples
uniform sampler2D superSampledImage;
uniform vec2 texelSize;
void main()
{
vec3 color = vec3(0.0);
color += texture(superSampledImage, TexCoords + vec2(-0.25, -0.25) * texelSize).rgb;
color += texture(superSampledImage, TexCoords + vec2( 0.25, -0.25) * texelSize).rgb;
color += texture(superSampledImage, TexCoords + vec2(-0.25, 0.25) * texelSize).rgb;
color += texture(superSampledImage, TexCoords + vec2( 0.25, 0.25) * texelSize).rgb;
FragColor = vec4(color * 0.25, 1.0);
}
MSAA (Multisample Anti-Aliasing)
MSAA is more efficient than SSAA. It evaluates the fragment shader once per pixel but evaluates depth/stencil at multiple sub-sample positions:
// Enable MSAA in OpenGL
glEnable(GL_MULTISAMPLE);
glfwWindowHint(GLFW_SAMPLES, 4); // Request 4x MSAA
// Create multisample framebuffer
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, width, height, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
flowchart LR
subgraph SSAA
A1[4x pixels] --> A2[4x shader] --> A3[Downsample]
end
subgraph MSAA
B1[1x pixels] --> B2[1x shader + 4x depth] --> B3[Resolve]
end
MSAA Resolve
#version 330 core
uniform sampler2DMS msaaTexture;
uniform int sampleCount;
void main()
{
vec3 color = vec3(0.0);
ivec2 texel = ivec2(gl_FragCoord.xy);
for (int i = 0; i < sampleCount; i++)
{
color += texelFetch(msaaTexture, texel, i).rgb;
}
FragColor = vec4(color / sampleCount, 1.0);
}
FXAA (Fast Approximate Anti-Aliasing)
FXAA is a post-Process technique that detects edges in the final image and applies blur along them:
uniform sampler2D sceneTexture;
uniform vec2 texelSize;
void main()
{
vec3 colorCenter = texture(sceneTexture, TexCoords).rgb;
// Edge detection using luminance
float lumaCenter = dot(colorCenter, vec3(0.299, 0.587, 0.114));
float lumaNorth = dot(texture(sceneTexture, TexCoords + vec2(0, 1) * texelSize).rgb, vec3(0.299, 0.587, 0.114));
float lumaSouth = dot(texture(sceneTexture, TexCoords + vec2(0, -1) * texelSize).rgb, vec3(0.299, 0.587, 0.114));
float lumaEast = dot(texture(sceneTexture, TexCoords + vec2(1, 0) * texelSize).rgb, vec3(0.299, 0.587, 0.114));
float lumaWest = dot(texture(sceneTexture, TexCoords + vec2(-1, 0) * texelSize).rgb, vec3(0.299, 0.587, 0.114));
// Check if this is an edge
float edgeX = abs(lumaEast + lumaWest - 2.0 * lumaCenter);
float edgeY = abs(lumaNorth + lumaSouth - 2.0 * lumaCenter);
if (edgeX > 0.1 || edgeY > 0.1)
{
vec2 blurDir = vec2(edgeX, edgeY);
// Blend with neighboring pixels along the edge direction
colorCenter = (texture(sceneTexture, TexCoords + blurDir * texelSize).rgb +
texture(sceneTexture, TexCoords - blurDir * texelSize).rgb +
colorCenter * 2.0) * 0.25;
}
FragColor = vec4(colorCenter, 1.0);
}
TAA (Temporal Anti-Aliasing)
TAA accumulates samples across multiple frames using sub-pixel jitter:
// Per-frame jitter (Halton sequence)
float halton(int index, int base)
{
float result = 0.0;
float f = 1.0;
while (index > 0)
{
f /= base;
result += f * (index % base);
index /= base;
}
return result;
}
// Generate jitter for current frame
int frame = 0;
vec2 jitter = vec2(halton(frame, 2) - 0.5, halton(frame, 3) - 0.5);
jitter /= vec2(screenWidth, screenHeight);
// Apply to projection matrix
projection[2].x += jitter.x;
projection[2].y += jitter.y;
// TAA resolve with history
uniform sampler2D currentFrame;
uniform sampler2D historyBuffer;
uniform vec2 velocity;
void main()
{
vec3 current = texture(currentFrame, TexCoords).rgb;
vec3 history = texture(historyBuffer, TexCoords - velocity).rgb;
// Clamp history to current frame neighborhood
vec3 neighborhoodMin = min(min(current, ...), ...);
vec3 neighborhoodMax = max(max(current, ...), ...);
history = clamp(history, neighborhoodMin, neighborhoodMax);
// Blend
float blendFactor = 0.05;
vec3 result = mix(current, history, blendFactor);
FragColor = vec4(result, 1.0);
}
Quality Comparison
| Method | Quality | Cost | Artifacts |
|---|---|---|---|
| None | Poor | 0% | Jagged edges |
| SSAA 4x | Excellent | +300% | None |
| MSAA 4x | Good | +50% | Alpha-test edges |
| FXAA | Fair | +5% | Blurred text |
| TAA | Very Good | +10% | Ghosting |
Common Errors & Mistakes
1. MSAA Disabled After Framebuffer Creation
Mistake: Enabling GL_MULTISAMPLE but not using a multisample framebuffer, so MSAA has no effect.
Fix: Use glTexImage2DMultisample and glFramebufferTexture2D(GL_FRAMEBUFFER, ..., GL_TEXTURE_2D_MULTISAMPLE, ...) for the FBO.
2. TAA Ghosting
Mistake: Moving objects leave trails because the history buffer contains outdated pixel data.
Fix: Apply motion vectors and neighborhood clamping to reject invalid history. Reduce blend factor for fast-moving objects.
3. FXAA Blurring Fine Details
Mistake: FXAA blurs text, UI elements, and fine textures because it cannot distinguish aliasing from detail.
Fix: Use MSAA or TAA for scenes with text or fine patterns. Apply FXAA only to the 3D scene layer, not the UI.
4. Incorrect Sample Pattern
Mistake: Using the same sub-pixel offset every frame for TAA, failing to accumulate new information.
Fix: Use a deterministic low-discrepancy sequence (Halton, Hammersley) that covers the pixel evenly over time.
Practice Questions
Question 1
Why is SSAA more expensive than MSAA?
Show answer
SSAA runs the fragment shader N times per pixel (N = sample count). MSAA runs the fragment shader once per pixel but computes depth/stencil at N positions, sharing the shader result.Question 2
What causes TAA ghosting and how do you fix it?
Show answer
Ghosting occurs when the history buffer contains outdated pixel data that is blended with the current frame. Fix by using motion vectors, neighborhood clamping to reject invalid history, and reducing blend factor for moving objects.Question 3
What is the main disadvantage of FXAA?
Show answer
FXAA blurs the entire image, especially affecting text, UI elements, and fine texture details. It cannot distinguish between intended detail and aliasing artifacts.Question 4
What are sub-pixel samples in MSAA?
Show answer
Sub-pixel samples are multiple depth/stencil test locations within each pixel. The pixel color is determined by averaging the covered sub-samples. Typical configurations are 2x, 4x, or 8x samples.Challenge
Implement a TAA system with motion vector generation, history buffer management, neighborhood clamping, and a per-frame Halton jitter sequence. Compare the quality against 4x MSAA and FXAA.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Author: DodaTech | Last updated: June 21, 2026
DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro