Course outline
Filtering and Mipmaps
In one line
A screen pixel rarely covers exactly one texel. Filtering decides how texels are blended when the picture is magnified or shrunk. Mipmaps and anisotropy keep a far floor from shimmering or turning to mush.
Think of it like this
Reading a poster from across a room. Close up you can read every letter. From far away, many letters fall inside one spot of your eye and blur together into grey. A smart printer could make a small copy for far viewing, so each spot of the eye gets one clean grey, and not a random letter that flickers when you move your head.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- magnification
- One texel covers more than one pixel: the picture is close and enlarged.
- minification
- More than one texel falls inside one pixel: the picture is far and shrunk.
- mipmap
- One of a chain of smaller, pre-averaged copies of a texture, each half the size in width and height.
- shimmer
- Flicker of far detail when the camera moves, because each pixel reads a different single texel every frame.
- anisotropic filtering
- Taking several samples along the stretched direction of a surface seen at a low angle, so it stays sharp.
Step by step
01See the shimmer cause
In the first example the left strip has no mipmaps. Far away, each pixel reads one texel of a fine checker: the result is a noisy pattern, which flickers as the camera moves.
02Use the default
The middle strip uses the default mipmaps. The noise is gone, replaced by an even grey: each pixel now reads the average of many squares. That is correct, but blurry far away.
03Add anisotropy
The right strip adds anisotropy = 16. The checker stays visible further down the strip before it turns grey, because the GPU samples along the long side.
04Watch the levels
The second example paints each mip level a colour. The floor shows bands (seen in a screenshot): orange close by (level 0), then yellow, green, teal, blue, violet, and white at the horizon (level 6, 1 × 1).
Try it yourself
- 1
Blend the levels
In the second example change
LinearMipmapNearestFiltertoLinearMipmapLinearFilter. The hard bands melt into a smooth rainbow: trilinear filtering blends the two nearest levels. - 2
Turn anisotropy off
In the first example set
t.anisotropy = 16to1. The right strip now looks like the middle one: a grey blur where it was crisp. Which value is best is a trade of sharpness for texture reads.
Code & diagrams
Try it: interactive
UV viewer: filtering
Pick the plane and set Repeat 4. Switch on NearestFilter for blocky pixels, then switch mipmaps off and on and watch the memory line (a third more with mipmaps).
Left: no mipmaps. Middle: the default. Right: anisotropy 16. The numbers 1006 and 1008 are the constants LinearFilter and LinearMipmapLinearFilter. A still picture cannot show flicker: look at the left strip while the page scrolls or run it in a window you resize.
Console
Run the scene to see its console.Expected console output
no mipmaps (Linear) | minFilter: 1006 | generateMipmaps: false | anisotropy: 1
mipmaps (the default) | minFilter: 1008 | generateMipmaps: true | anisotropy: 1
mipmaps + anisotropy 16 | minFilter: 1008 | generateMipmaps: true | anisotropy: 16A custom chain, one colour per level: a DataTexture with mipmaps set by hand. LinearMipmapNearestFilter picks one level per pixel, so the bands have hard edges. This is the same chain three.js builds for you, with the averaged picture instead of a colour.
Console
Run the scene to see its console.Expected console output
levels: 64x64, 32x32, 16x16, 8x8, 4x4, 2x2, 1x1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Switch off mipmaps on a far floor
In the first example set the middle strip's setup to (t) => { t.generateMipmaps = false; t.minFilter = THREE.LinearFilter; }, the same as the left.
Myth vs fact
Myth
Mipmaps are only for speed.
Fact
Their main job is image quality: they stop far-away shimmer. They cost about a third more memory, and they make far texture reads cheaper because small levels fit the GPU's cache.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The GPU chooses the mip level from how fast the UVs change between neighbouring pixels (screen-space derivatives). This is why the level is per pixel, not per object, and why a custom shader that changes UVs wildly can pick the wrong level (Phase 12).
- ▸
Mipmaps are built by the GPU (
generateMipmap) for images; aDataTextureor compressed texture (KTX2) can bring its own chain intexture.mipmaps, as the second example does. KTX2 files normally carry their mipmaps inside.
Remember this
- 1
The problem this solves: a 1024-pixel floor viewed from far away has hundreds of texels under one screen pixel. If the GPU reads only one of them, the pixel shows a random texel, and when the camera moves it flickers: shimmer. If the floor is seen up close, one texel covers many pixels and looks blocky or blurry. Filtering settings choose how to cope.
- 2
Magnification means one texel is bigger than a pixel (you are close).
magFilterisLinearFilter(the default: blend the four nearest texels, smooth) orNearestFilter(take one texel: hard squares, the look of pixel art). Minification means many texels fit in a pixel (you are far).minFilteris where the real choices live. - 3
Mipmaps are pre-shrunk copies. For a 1024 × 1024 picture, three.js builds 512, 256, 128... down to 1 × 1: 11 levels (
floor(log2(1024)) + 1). Each level is a quarter of the one before, so the whole chain costs about one third more memory (Topic 6.6). The GPU picks the level whose texels are about pixel size, so far surfaces read from a small, pre-averaged picture.generateMipmaps(default true) builds them when the texture is uploaded. - 4
The six filters. The default
LinearMipmapLinearFilter(trilinear) blends two neighbouring levels and four texels in each: smooth.LinearMipmapNearestFilterpicks the nearest level, so you can see a hard line where the level changes.NearestMipmapNearestFilterandNearestMipmapLinearFilterdo the same with one texel per level (blocky).LinearFilterandNearestFilterignore mipmaps: cheap, but far surfaces shimmer. The second example paints each level a colour so you can see the GPU choosing. - 5
Anisotropy fixes the grazing angle. A floor seen at a low angle is squeezed: each pixel covers a long thin patch of texels. Plain mipmaps pick a level for the longer side, and everything blurs.
texture.anisotropy = 16lets the GPU take several samples along the long side: sharp far floors for more texture reads. The number is a request; the GPU's limit isrenderer.capabilities.getMaxAnisotropy()(often 16, and some devices allow less). three.js clamps it for you. - 6
Non-power-of-two sizes work. A 300 × 200 picture gets mipmaps and
RepeatWrappingin WebGL 2, which r186 requires. Old advice to resize to 256 or 512 is out of date for WebGL 2 (and wastes memory when it enlarges a picture).
Explain it without notes
Why does a far floor flicker when the camera moves, and what is the fix?
What does anisotropic filtering add that mipmaps do not?
Practice
A road texture looks blurry in the distance but sharp near the camera. Which one setting do you change, and what does it cost?
How many mip levels does a 2048 × 2048 texture have?
Done when you can
Say when a texture is magnified and when it is minified
Explain what a mip chain is and what it costs
Pick between Nearest, Linear and the mipmap filters for a floor and for pixel art
Say what anisotropy fixes