How Mipmaps Work

An interactive deep-dive into the GPU trick that turns shimmering aliased textures into smooth, fast rendering — from first principles.

scroll to begin ↓
Act I

Tiny Textures, Angry Pixels

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:

drag slider to tilt the plane

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.

Crank the checker size up to 64 and tilt the plane steeply. The distant region becomes pure visual noise. Then click "No Mipmap" to toggle mipmapping on — watch the noise melt away into a smooth gradient.

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.

Act II

One Pixel, Many Texels

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.

drag the orange square across the texture

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).

Start with a 1×1 footprint — the sample and the average match perfectly. Now slide it up to 8×8 or higher. Drag the footprint around and watch how the single sample flickers wildly between black and white, while the correct average stays a steady grey. That flicker IS the aliasing you saw in Act I.
A single screen pixel covers many texels, but the GPU only samples one point. The mismatch between what it samples and what it should see is the root cause of texture aliasing.
Act III

The Brute-Force Fix

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.

Act IV

The Mipmap Pyramid

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:

click any texel to trace its origin

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.

Click a texel in the smallest levels (the tiny ones on the right). Follow the orange traces back — each texel at level 3 represents the average of an 8×8 block in the original! Now try the different texture types and watch how the averaging behaves with smooth gradients vs. sharp patterns.

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.

A mipmap is a pre-computed pyramid of progressively smaller copies. Each level stores the average colours at a coarser resolution — turning an expensive multi-sample problem into a single cheap lookup.

The Memory Cost

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.

Act V

Choosing the Right Level

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.

Tilt the plane to be nearly flat — see how the entire surface goes red/yellow because every pixel now covers a huge texture region. Then tilt it upright and watch the levels drop back to blue as each pixel maps to fewer texels.
// The GPU's mip level formula (simplified)
vec2 dx = dFdx(uv);UV change across 1 pixel in X
vec2 dy = dFdy(uv);UV change across 1 pixel in Y
float d = max(dot(dx,dx), dot(dy,dy));largest rate of change
float mipLevel = 0.5 * log2(d);log₂ gives us the level

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.

Act VI

Between Two Levels

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.

Toggle between modes a few times with a steep tilt. The banding in nearest-mip mode is most visible where the plane is at a moderate angle. Trilinear eliminates it completely.
Trilinear filtering = bilinear interpolation within each of two adjacent mip levels + a linear blend between them. Smooth transitions, no visible seams.
Act VII

The Playground

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.

Set the filter to "None" and tilt steeply — pure aliasing chaos. Switch to "Trilinear" — silky smooth. Now play with mip bias: positive values force the GPU to use higher mip levels (blurrier), negative values force lower levels (sharper but more aliased). Game developers tweak this bias to balance sharpness against shimmering. Turn on the heatmap to see level selection in real time.

Why Mipmaps Also Make Things Faster

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.

Mipmaps use just 33% extra memory, eliminate aliasing, enable smooth level-of-detail transitions, and improve cache performance. It's one of the best deals in computer graphics.