Command Palette

Search for a command to run...

Hectal
Course outline
Topic 15.4Advanced ~21 min · step 4 of 6

Anti-Aliasing

In one line

Slanted edges on a pixel grid look like steps. MSAA smooths them while drawing, FXAA and SMAA smooth the finished picture. The catch: the canvas's MSAA does not reach a composer's pictures unless you ask for it.

0/6 · 0%

Think of it like this

Draw a diagonal line on squared paper by colouring whole squares. It looks like a staircase. Now colour the squares along the line a pale grey instead of all-or-nothing: from far away the stairs vanish. Anti-aliasing is deciding that pale grey for every edge pixel.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

aliasing
The stair-step look of slanted edges on a pixel grid.
MSAA
Multisample anti-aliasing: several sample points per pixel while drawing triangles.
FXAA
Fast approximate anti-aliasing: one pass that blurs detected edges in the finished picture.
SMAA
Subpixel morphological anti-aliasing: finds edge shapes in the finished picture and blends along them.
samples
How many points per pixel MSAA tests. 4 is the usual choice.

Step by step

01Draw a white wedge on black

Every edge is slanted: the hardest case. A "soft pixel" is one that is neither black nor white (red between 10 and 245). More soft pixels along the edge means smoother edges.

02Compare the canvas with and without MSAA

antialias: false gives 0 soft pixels: pure staircase. antialias: true gives 450.

03Add a composer

The same renderer, with antialias: true, drawing through a plain EffectComposer gives 0 again. The flag still says true, but the picture the canvas receives was made in targets with no samples.

04Bring the smoothing back

samples: 4 on the composer's target gives 450. SMAAPass after OutputPass gives 595 (after its lookup pictures load). The FXAA ShaderPass gives 759: the most soft pixels, because it blurs a wide band.

Try it yourself

  1. 1

    More samples

    In the first example change samples: 4 to samples: 8 inside the new THREE.WebGLRenderTarget(300, 150, { ... }) line (the label text still says 4). It still prints soft edge pixels 450, because the software GPU used for these runs allows 4 samples at most (renderer.capabilities.maxSamples printed 4 here) and a larger request is clamped without an error. On your GPU that number says what you can ask for.

  2. 2

    No samples

    Change samples: 4 to samples: 0 in the same line. The line labelled composer, samples: 4: (the label is only text) now prints soft edge pixels 0: no samples, no smoothing.

Code & diagrams

Soft edge pixels with each method New tab

The counts come from this software GPU: another GPU's MSAA pattern gives a different number, but 0 stays 0. The pause lets SMAA's lookup pictures load.

idle

Console

Run the scene to see its console.

Expected console output

no anti-aliasing:        soft edge pixels 0
antialias: true:         soft edge pixels 450
same renderer + composer: soft edge pixels 0
composer, samples: 4:    soft edge pixels 450
composer + SMAA:         first frame: 0
composer + SMAA:         soft edge pixels 595
composer + FXAA:         soft edge pixels 759
Fine detail under SMAA and FXAA New tab

Sharpness is the average jump between neighbouring pixels on one row: 128 for 1-pixel white lines, 3 black pixels apart.

idle

Console

Run the scene to see its console.

Expected console output

no pass        sharpness 128
SMAA           sharpness 128
FXAA           sharpness 58

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

antialias: true and a composer

In the first example read the line same renderer + composer:.

terminal
$ Run
── what you'll see ──
same renderer + composer: soft edge pixels 0

Break #2

SMAA on the first frame

In the first example read the line composer + SMAA: first frame:.

terminal
$ Run
── what you'll see ──
composer + SMAA: first frame: 0

Myth vs fact

Myth

antialias: true smooths everything the canvas shows.

Fact

It smooths only what the renderer draws onto the canvas directly. A composer, a render target or a shader effect does its own thing.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    MSAA memory grows with the samples: a half-float target at 1920 × 1080 with 4 samples holds 1920 × 1080 × 8 × 4 = 66,355,200 bytes (about 63 MB) of colour, plus the resolved copy. That is why phones often skip MSAA for SMAA, or nothing.

  • ▸

    In the WebGPU node pipeline pass(scene, camera) copies renderer.samples into its target (it printed 4 with antialias: true on a real WebGPU adapter and in the WebGL 2 fallback), so MSAA survives post-processing there without extra code. The third example of Topic 15.2 prints it.

Remember this

  1. 1

    The problem this solves: a pixel is either inside a shape or outside it, so edges at an angle show steps ("jaggies"), and they crawl when the camera moves. The fix is to estimate how much of each edge pixel the shape covers and blend the colours.

  2. 2

    MSAA (multisample anti-aliasing) asks the GPU to test several points inside each pixel while drawing triangle edges. It is exact for geometry edges, cheap on the GPU, and costs memory in proportion to the samples. new WebGLRenderer({ antialias: true }) asks the browser for an anti-aliased canvas (browsers commonly give 4 samples, but the number is the browser's choice and some browsers or GPUs refuse).

  3. 3

    The catch with a composer. The canvas is MSAA, but a composer's pictures are plain render targets with no samples. Everything is drawn into them first, so the smooth edges are gone before the canvas sees the picture. The first example: a wedge has 450 soft edge pixels with antialias: true, and 0 once the same renderer draws through an EffectComposer. (The counts come from the software GPU these examples run on; another GPU gives other counts, but 0 stays 0.)

  4. 4

    Three fixes. (1) Give the composer's target samples: new WebGLRenderTarget(w, h, { type: HalfFloatType, samples: 4 }). The composer clones it for its second target. 450 soft pixels again. (2) SMAAPass, a post-process that finds edges in the finished picture: 595 soft pixels. (3) FXAA, a one-pass blur of detected edges: 759.

  5. 5

    What each costs and misses. MSAA costs memory (4 × the colour data) and cannot smooth edges made by shaders or textures. SMAA is a few full-screen passes and keeps fine detail. FXAA is one pass, the cheapest, but it blurs everything that looks like an edge, textures too: in the second example a line pattern drops from sharpness 128 to 58 under FXAA, while SMAA leaves it at 128.

  6. 6

    SMAA starts a moment late. SMAAPass loads two lookup pictures from embedded image data, and image decoding is asynchronous. In the first example SMAA's first frame has 0 soft pixels. Later frames have 595.

Explain it without notes

01

Why do edges become jagged when you add a composer to a renderer created with antialias: true?

02

Compare MSAA, SMAA and FXAA for a showroom page for phones.

Practice

01

Create a composer whose pictures keep 4-sample MSAA.

02

Add FXAA at the right place in the chain Render, Bloom, Output.

Done when you can

  • Explain why a composer loses the canvas's MSAA

  • Restore smooth edges with target samples, SMAA or FXAA

  • Place SMAA and FXAA after OutputPass

  • Name what each method costs and what it blurs