O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Fight Aliasing with Filtering

Is a texture a collection of discrete texels, or is it a continuous function across [0, 1]? This is a dangerous question to ask a graphics geek; it’s a bit like asking a physicist if a photon is a wave or a particle.

When you upload a texture to OpenGL using glTexImage2D, it’s a collection of discrete texels. When you sample a texture using normalized texture coordinates, it’s a bit more like a continuous function. You might recall these two lines from the rendering engine:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

What’s going on here? The first line sets the minification filter; the second line sets the magnification filter. Both of these tell OpenGL how to map those discrete texels into a continuous function.

More precisely, the minification filter specifies the scaling algorithm to use when the texture size in screen space is smaller than the original image; the magnification filter tells OpenGL what to do when the texture size is screen space is larger than the original image.

The magnification filter can be one of two values:

GL_NEAREST

Simple and crude; use the color of the texel nearest to the texture coordinate.

GL_LINEAR

Indicates bilinear filtering. Samples the local 2×2 square of texels and blends them together using a weighted average. The image on the far right in Figure 5-4 is an example of bilinear magnification applied to a simple 8×8 monochrome texture.

Bilinear texture filtering. From left to right: original, minified, magnified

Figure 5-4. Bilinear texture filtering. From left to right: original, minified, magnified

The minification filter supports the same filters as magnification and adds four additional filters that rely on mipmaps, which are “preshrunk” images that you need to upload separately from the main image. More on mipmaps soon.

The available minification modes are as follows:

GL_NEAREST

As with magnification, use the color of the nearest texel.

GL_LINEAR

As with magnification, blend together the nearest four texels. The middle image in Figure 5-4 is an example of bilinear minification.

GL_NEAREST_MIPMAP_NEAREST

Find the mipmap that best matches the screen-space size of the texture, and then use GL_NEAREST filtering.

GL_LINEAR_MIPMAP_NEAREST

Find the mipmap that best matches the screen-space size of the texture, and then use GL_LINEAR filtering.

GL_LINEAR_MIPMAP_LINEAR

Perform GL_LINEAR sampling on each of two “best fit” mipmaps, and then blend the result. OpenGL takes eight samples for this, so it’s the highest-quality filter. This is also known as trilinear filtering.

GL_NEAREST_MIPMAP_LINEAR

Take the weighted average of two samples, where one sample is from mipmap A, the other from mipmap B.

Figure 5-5 compares various filtering schemes.

Texture filters (from top to bottom: nearest, bilinear, and trilinear)

Figure 5-5. Texture filters (from top to bottom: nearest, bilinear, and trilinear)

Deciding on a filter is a bit of a black art; personally I often start with trilinear filtering (GL_LINEAR_MIPMAP_LINEAR), and I try cranking down to a lower-quality filter only when I’m optimizing my frame rate. Note that GL_NEAREST is perfectly acceptable in some scenarios, such as when rendering 2D quads that have the same size as the source texture.

First- and second-generation devices have some restrictions on the filters:

  • If magnification is GL_NEAREST, then minification must be one of GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, or GL_NEAREST_MIPMAP_LINEAR.

  • If magnification is GL_LINEAR, then minification must be one of GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, or GL_LINEAR_MIPMAP_LINEAR.

This isn’t a big deal since you’ll almost never want a different same-level filter for magnification and minification. Nevertheless, it’s important to note that the iPhone Simulator and newer devices do not have these restrictions.

Boosting Quality and Performance with Mipmaps

Mipmaps help with both quality and performance. They can help with performance especially when large textures are viewed from far away. Since the graphics hardware performs sampling on an image potentially much smaller than the original, it’s more likely to have the texels available in a nearby memory cache. Mipmaps can improve quality for several reasons; most importantly, they effectively cast a wider net, so the final color is less likely to be missing contributions from important nearby texels.

In OpenGL, mipmap zero is the original image, and every following level is half the size of the preceding level. If a level has an odd size, then the floor function is used, as in Equation 5-1.

Equation 5-1. Mipmap sizes

Mipmap sizes

Watch out though, because sometimes you need to ensure that all mipmap levels have an even size. In another words, the original texture must have dimensions that are powers of two. We’ll discuss this further later in the chapter. Figure 5-6 depicts a popular way of neatly visualizing mipmaps levels into an area that’s 1.5 times the original width.

To upload the mipmaps levels to OpenGL, you need to make a series of separate calls to glTexImage2D, from the original size all the way down to the 1×1 mipmap:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, 
pImageData0);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 8, 8, 0, GL_RGBA,
             GL_UNSIGNED_BYTE, pImageData1);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 4, 4, 0, GL_RGBA,
             GL_UNSIGNED_BYTE, pImageData2);
glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 2, 2, 0, GL_RGBA,
             GL_UNSIGNED_BYTE, pImageData3);
glTexImage2D(GL_TEXTURE_2D, 4, GL_RGBA, 1, 1, 0, GL_RGBA,
             GL_UNSIGNED_BYTE, pImageData4);
Mipmap visualization

Figure 5-6. Mipmap visualization

Usually code like this occurs in a loop. Many OpenGL developers like to use a right-shift as a sneaky way of halving the size at each iteration. I doubt it really buys you anything, but it’s great fun:

for (int level = 0; 
     level < description.MipCount; 
     ++level, width >>= 1, height >>= 1, ppData++)
{
    glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, width, height, 
                 0, GL_RGBA, GL_UNSIGNED_BYTE, *ppData);
}

If you’d like to avoid the tedium of creating mipmaps and loading them in individually, OpenGL ES can generate mipmaps on your behalf:

// OpenGL ES 1.1
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, ...);

// OpenGL ES 2.0
glTexImage2D(GL_TEXTURE_2D, 0, ...);
glGenerateMipmap(GL_TEXTURE_2D);

In ES 1.1, mipmap generation is part of the OpenGL state associated with the current texture object, and you should enable it before uploading level zero. In ES 2.0, mipmap generation is an action that you take after you upload level zero.

You might be wondering why you’d ever want to provide mipmaps explicitly when you can just have OpenGL generate them for you. There are actually a couple reasons for this:

  • There’s a performance hit for mipmap generation at upload time. This could prolong your application’s startup time, which is something all good iPhone developers obsess about.

  • When OpenGL performs mipmap generation for you, you’re (almost) at the mercy of whatever filtering algorithm it chooses. You can often produce higher-quality results if you provide mipmaps yourself, especially if you have a very high-resolution source image or a vector-based source.

Later we’ll learn about a couple free tools that make it easy to supply OpenGL with ready-made, preshrunk mipmaps.

By the way, you do have some control over the mipmap generation scheme that OpenGL uses. The following lines are valid with both ES 1.1 and 2.0:

glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // this is the default

Modifying ModelViewer to Support Mipmaps

It’s easy to enable mipmapping in the ModelViewer sample. For the ES 1.1 rendering engine, enable mipmap generation after binding to the texture object, and then replace the minification filter:

glGenTextures(1, &m_gridTexture);
glBindTexture(GL_TEXTURE_2D, m_gridTexture);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// ...
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x,
             size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

For the ES 2.0 rendering engine, replace the minification filter in the same way, but call glGenerateMipmap after uploading the texture data:

glGenTextures(1, &m_gridTexture);
glBindTexture(GL_TEXTURE_2D, m_gridTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// ...
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, 
             size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glGenerateMipmap(GL_TEXTURE_2D);

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required