Lesson 19: Mipmapping

Far Away Textures

Using just what we've learned so far, textures can look rather bad when they're far away from the camera. For example, you can take the source code for this lesson, and go to the following code:

void drawScene() {
    //...
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR_MIPMAP_LINEAR);
    //...
}

You can uncomment the first two lines shown above and comment out the rest. If you do this, you have the following scene:

Aliased checkerboard scene

It's supposed to be a checkerboard extending infinitely in each direction. It's drawn as a large quadrilateral with the following texture applied to it:

Checkerboard texture

Off in the distance, the checkerboard scene looks fairly cruddy. It suffers from something called "aliasing".

Why can't we get this scene right? Each pixel that we draw to the screen might cover a large area of the texture image, as demonstrated in the following animated picture:

Texel coverage

The picture depicts an 11x11 grid of pixels, and shows that one pixel covers a large area of the texture. What we would want to do, more or less, is to average together all of the texture pixels covered by the screen pixel. If you remember from the lesson on textures, we took each screen pixel to be a point on a texture, and only averaged together at most four pixels from the texture image. The following picture from the lesson on textures demonstrates how we've been doing texture mapping:

Texture mapping diagram

Mipmapping

We'll fix the checkerboard scene using something called "mipmapping". This picture shows an example of a mipmap:

Mipmap

The mipmap is just a bunch of images. First, we have the original texture image, which in this case is 256x256. Then, we have a 128x128 version of the texture image, as well as a 64x64 version, a 32x32 version, and so on all the way down to a 1x1 version. Each image is already anti-aliased. In other words, the pixels in the smaller images span a large area of the original texture image, and these pixels are the average of the pixels they cover in the original.

For textures that are far from the camera, we can approximate the average of the texture pixels spanned by a single screen pixel by using one of the smaller versions of the texture. We can take the version of the texture for which one pixel is nearest to the area covered by the screen pixel. Then, we can just use that image for texture mapping.

Mipmapping Code

To add mipmapping, we'll first add a function called "loadMipmappedTexture".

//Makes the image into a mipmapped texture, and returns the id of the texture
GLuint loadMipmappedTexture(Image *image) {
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);
    gluBuild2DMipmaps(GL_TEXTURE_2D,
                      GL_RGB,
                      image->width, image->height,
                      GL_RGB,
                      GL_UNSIGNED_BYTE,
                      image->pixels);
    return textureId;
}

This function looks a lot like the loadTexture function we've been using. The difference is that we call gluBuild2DMipmaps instead of glTexImage2D. The call uses the pixel data (image->pixels) to make the OpenGL texture image and all of the smaller versions of the images. It gets everything set up to use mipmapping.

void drawScene() {
    //...
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR_MIPMAP_LINEAR);
    //...
}

To use mipmapping, we just send a new argument to glTexParameteri. When the texture is far away, we use GL_LINEAR_MIPMAP_LINEAR.

Actually, there are four options at our disposal for mipmapping: GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, and GL_NEAREST_MIPMAP_NEAREST. The first linear / nearest indicates the type of mapping that we use on the texture image: blurry or blocky. The second linear / nearest indicates whether we want to use just the smaller version of the texture closest to the appropriate size, or whether we want to use the two closest versions and average the results together.

With this code, we can use mipmapping to make far-away textures look good in OpenGL.

Next is "Lesson 20: Materials".