Mipmapping

How to 3D

Chapter 9: Textures

Mipmapping

The curse of texture magnification is blurring. There are not enough texels when the texture is blown up on a small display, so the texturing hardware interpolates new ones that smooth out the color transitions. Sometimes we have the opposite situation: the texture has a lot of texels that we are trying to fit on a very small display. This is minification.

Minification has a curse of its own. When there are more texels than fragments to display them, some texels may be squeezed out, and features of the texture will not be rendered. Such undersampling can be seen in this rendering of a checkerboard texture:

At the far center of the checkerboard, you can see what looks like a stretched row of checks. That row is actually several rows. They appear as one row because the complementary rows between them have been skipped over. Rotate the floor and you will see other distracting patterns on the horizon.

When a high-frequency pattern is inadvertently reproduced at a lower frequency, we have aliasing. If a phone camera records 30 frames per second, but a hummingbird flaps its wings 60 times per second, the video will show a much slower bird. Entire rows of the checkerboard are missing. Changing the minification filter to linear doesn't fix aliasing. We are skipping too much of the high-frequency pattern. There are simply not enough fragments sampling the texture for it to be faithfully reproduced on the horizon of this checkerboard.

A graphics developer named Lance Williams proposed a fix for texture aliasing in 1983. He suggested providing a pyramid of different resolutions of a texture. Level 0 of the pyramid is the original texture. Level 1 is a version with half the dimensions. Level 2 is a version with a quarter of the dimensions. And so on. At the top of the pyramid is a 1×1 version of the texture. Generating the pyramid is easier if level 0 has dimensions that are powers of 2. Powers of 2 split in half cleanly.

The checkerboard has these four levels leading to the top of its pyramid:

The images are scaled up so that you can see them. All levels are a clean checkerboard pattern save for the very top 1x1 image, which blends the white and black into a single gray pixel.

The pyramid of images is called a mipmap. Mip- stands for multum in parvo, which is Latin for much in little. The word little refers to the fact that the extra resolutions increase the overall size of the texture by only a third.

We can generate and upload the other levels of a texture ourselves with texImage, or we can let WebGL do it for us by calling generateMipmap on the currently bound texture:

gl.generateMipmap(gl.TEXTURE_2D);
gl.generateMipmap(gl.TEXTURE_2D);

When a texel and pixel are about the same size, level 0 is used on a texel lookup. As the texels get smaller than the pixel, the lower-resolution levels are used. Try switching the mipmap filter in the renderer below. Which combination of minification filter and mipmap filter look most pleasing?

Setting the mipmap filter to nearest makes the texture lookup pull from the mipmap level closest to the pixel size. Linear makes the texture lookup blend the colors from two surrounding mipmap levels.

As we build the mipmap pyramid, the texels from the bigger level are blended together to form the smaller level. The blending is what makes the horizon gray. Mipmapping effectively prefilters the texture so that its details are smoothed out rather than lost altogether.

Adding mipmapping to a renderer requires just two steps. The first step we have already seen: we must upload or automatically generate the mipmap levels. The second step is to use a minification filter that makes the texturing hardware pull colors from the smaller resolution levels. With two kinds of interpolation between texels and three mipmap settings, a total of six different minification filters are available:

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);

The controls in the render above let you examine each of these six possible minification filters.

Summary

Textures offer the illusion of fine detail at a much lower cost than extra triangles. They don't increase the number of times the shaders run. Rather they provide a map of albedo values. The textures are read from files or procedurally generated and then uploaded to the graphics card. Each vertex has a 2-vector attribute that pins it to a two-dimensional location on the texture. Shaders look up the color by passing these texture coordinates to a texture unit. If the coordinates are out-of-bounds, they are wrapped or clamped. Textures are discrete and will likely need to stretch and shrink to fit a surface. The graphics card pretends they are continuous by applying magnification and minification filters to produce a color at any location. It may blerp between surrounding texels or round to the nearest neighbor. High-frequency patterns are often undersampled and appear as lower-frequency aliases. These can be smoothed out by pre-filtering the images into a pyramid of mipmap levels.

← Magnifying Textures