An interactive deep-dive into the GPU trick that turns shimmering aliased textures into smooth, fast rendering — from first principles.
Let's start with something you've probably seen before — a checkerboard texture on a flat surface stretching off into the distance. In a perfect world, the distant squares would just get tinier and tinier until they blend into a smooth grey. But GPUs don't live in a perfect world.
Take a look at what actually happens:
See that shimmering, noisy mess in the distance? That's aliasing — and it's one of the most common visual artifacts in real-time graphics. The closer parts of the surface look fine, but as the checkerboard recedes, it disintegrates into a chaotic static pattern.
This isn't a bug in the texture or the shader. It's a fundamental sampling problem. To understand why, we need to look at what happens when a single pixel on your screen tries to represent a large region of the texture.
Here's the core of the problem. When the GPU rasterises a triangle and needs to figure out what colour a screen pixel should be, it looks up a position in the texture — a UV coordinate — and reads the colour at that point. Just one single point.
For nearby surfaces, that's fine. Each pixel maps to roughly one texel (a pixel in the texture), so the one-point lookup gives a good answer. But for distant surfaces, a single screen pixel might cover hundreds of texels. And the GPU is still only sampling one of them.
The left panel shows a checkerboard texture with a grid overlay — each cell is one texel. The orange square is the "footprint" of a single screen pixel — the region of the texture it covers. On the right, you see two things: the single point sample (what the GPU actually reads) and the correct average (what the pixel should look like).
OK, so the problem is that we need the average colour of a region, but we're only reading one point. The obvious fix: just read all the texels inside the pixel's footprint and average them.
This is called supersampling, and it produces perfect results. The only problem? It's absurdly expensive.
As the pixel footprint grows, the number of texture reads explodes — it's the area of the footprint, so it scales quadratically. A pixel covering a 16×16 region needs 256 reads. At 32×32, it's 1,024 reads. Per pixel. Per frame. At 60 FPS on a 4K screen, that's... let's just say the GPU would not be happy.
We need a way to get "the average colour of this region" in a single lookup. And that's exactly what a mipmap gives us.
Here's the core idea, and it's beautifully simple: pre-compute all the averages ahead of time.
Take your original texture (let's say it's 256×256). Now create a half-sized copy (128×128) where each texel is the average of a 2×2 block from the original. Then halve again (64×64), averaging 2×2 blocks from the previous level. Keep going — 32, 16, 8, 4, 2, all the way down to a single 1×1 texel that contains the average colour of the entire texture.
This stack of progressively smaller images is called a mipmap (from the Latin multum in parvo — "much in a small space"). Let's see it in action:
Each level is exactly half the width and height of the one before it. When you click on a texel at any level, the orange lines trace back to show which texels from the previous level were averaged to produce it — all the way back to the original.
Now, when the GPU needs to shade a pixel that covers, say, an 8×8 region of the original texture, it doesn't need 64 reads. It can just look up the corresponding texel in level 3 (which is already the average of an 8×8 block) with a single read. One lookup, correct answer, done.
You might be wondering: how much extra memory does this pyramid cost? Each level has 1/4 the pixels of the one before it, so the total extra storage is:
1/4 + 1/16 + 1/64 + 1/256 + ··· = 1/3
The entire mipmap chain costs just 33% extra memory on top of the original texture. That's a remarkably small price for what we get in return.
So we've got this pyramid of pre-averaged textures. But how does the GPU decide which level to sample from for any given pixel?
The key question is: how much of the texture does this pixel cover? If a pixel covers roughly 1 texel, use level 0 (the original). If it covers about 4×4 texels, use level 2. If it covers 16×16, use level 4. The GPU needs to measure the pixel's "footprint" in texture space.
It does this using screen-space derivatives — it looks at how the texture coordinates (UV) change from one pixel to its neighbours. If the UVs change rapidly across adjacent pixels, that means the texture is compressed into a small screen area, so each pixel covers many texels. Large UV change → high mip level.
In shader code, these are the dFdx and dFdy functions — the partial derivatives of the UV coordinates with respect to screen X and screen Y. The GPU computes these automatically by running pixels in 2×2 quads and differencing the UVs.
The heatmap overlay colours each pixel by which mip level it samples from. Blue is level 0 (full detail), green is level 2–3, yellow is level 4–5, and red is the highest levels (most compressed). Notice how it transitions smoothly from high-detail blue in the foreground to low-detail red at the horizon.
The log2 is the key — because each mip level halves the resolution, we need the logarithm to convert "pixel covers N texels" into "use level log₂(N)". A footprint of 8 texels wide → log₂(8) = level 3.
There's one more problem to solve. The mip level formula gives us a continuous number — say, 2.7 — but our mipmap only has integer levels: 0, 1, 2, 3. If we just snap to the nearest level, we get visible "seams" where one level suddenly switches to the next. On a surface receding into the distance, you can see these as distinct bands.
The fix is trilinear filtering: sample from the two nearest mip levels and blend between them based on the fractional part. Level 2.7 means 70% from level 3, 30% from level 2. Each of those samples also uses bilinear interpolation within its level — so the full pipeline is bilinear × bilinear + blend = trilinear.
In "Nearest Mip" mode, watch for the bands where one mip level abruptly switches to the next — they show up as sudden changes in sharpness. Toggle to "Trilinear" and those transitions dissolve into smooth gradients. Check "Show seams" to see the exact boundaries highlighted.
Now you've seen all the pieces — aliasing, footprints, the mipmap pyramid, level selection, and trilinear filtering. Here's a playground to put it all together. You've got full control over the filtering mode, mip bias, texture pattern, and viewing angle. Play with the settings and see how everything interacts.
Here's a nice bonus: mipmaps don't just look better — they're faster than unfiltered texturing. This sounds counterintuitive (we added more data!), but it comes down to cache coherence.
When a distant surface is sampled without mipmaps, adjacent pixels jump to wildly different texel locations in the full-res texture. That's terrible for the GPU's texture cache — it's constantly fetching new cache lines from scattered memory locations. With mipmaps, distant surfaces sample from a small, low-res mip level that fits entirely in cache. Every read is a cache hit.
So mipmaps are that rare optimisation: better quality and better performance. No wonder they're used everywhere — every 3D game, every rendering engine, every GPU since the late 1990s.