Mipmapping

If you've played around with our previous examples and let the cube move further away from the camera, you might have noticed that the texture starts to looks grainy and full of little artifacts the smaller the cube gets. This effect is called aliasing, a prominent effect in all types of signal processing. Figure 11-8 shows you the effect on the right side and the result of applying a technique called mipmapping on the left side.

Mipmapping
Figure 11-8. Aliasing artifacts on the right; the results of mipmapping on the left

I won't go into the details of why aliasing happens; all you need to know is how to make objects look better. That's where mipmapping comes in. [Au: OK? CE]

They key to fixing aliasing problems is to use lower-resolution images for parts of an object that are smaller on screen or further away from the view point. This is usually called a mipmap pyramid or chain. Given an image in its default resolution, say 256! 256 pixels, we create smaller versions of it, dividing the sides by two for each level of the mipmap pyramid. Figure 11-9 shows the crate texture with the various mipmap levels.

Texture Raw 256x256

256x256

Figure 11-9. A mipmap chain

256x256

Figure 11-9. A mipmap chain

To make a texture mipmapped in OpenGL ES we have to do two things:

Set the minification filter to one of the GL_XXX_MIPMAP_XXX constants, usually GL_LINEAR_MIPMAP_NEAREST.

Create the images for each mipmap chain level by resizing the original image and upload them to OpenGL ES. The mipmap chain is attached to a single texture, not multiple textures.

To resize the base image for the mipmap chain we can simply use the Bitmap and Canvas classes the Android API provides us with. Let us modify the Texture class a little. Listing 11-8 shows you the code.

Listing 11-8. Texture.java, Our Final Version of the Texture Class package com.badlogic.androidgames.framework.gl;

import java.io.IOException; import java.io.InputStream;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.opengl.GLUtils;

import com.badlogic.androidgames.framework.FileIO; import com.badlogic.androidgames.framework.impl.GLGame; import com.badlogic.androidgames.framework.impl.GLGraphics;

public class Texture { GLGraphics glGraphics; FileIO fileIO; String fileName; int textureId; int minFilter; int magFilter; public int width; public int height; boolean mipmapped;

We add only one new member, called mipmapped, which stores whether the texture has a mipmap chain or not.

public Texture(GLGame glGame, String fileName) { this(glGame, fileName, false);

public Texture(GLGame glGame, String fileName, boolean mipmapped) { this.glGraphics = glGame.getGLGraphics(); this.fileIO = glGame.getFileIO(); this.fileName = fileName; this.mipmapped = mipmapped; load();

For compatibility we leave the old constructor in which calls the new constructor. The new constructor takes a third argument that lets us specify whether we want the texture to be mipmapped or not.

private void load() {

GL10 gl = glGraphics.getGL(); int[] textureIds = new int[1]; gl.glGenTextures(1, textureIds, 0); textureId = textureIds[0];

InputStream in = null; try {

in = fileIO.readAsset(fileName);

Bitmap bitmap = BitmapFactory.decodeStream(in);

createMipmaps(gl, bitmap); } else {

gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

setFilters(GL10.GL_NEAREST, GL10.GL_NEAREST);

gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);

width = bitmap.getWidth();

height = bitmap.getHeight();

bitmap.recycle();

throw new RuntimeException("Couldn't load texture '" + fileName + , e);

The load() method stays pretty much the same as well. The only addition is the call to createMipmaps() in case the texture should be mipmapped. Non-mipmapped Texture instances are created as before.

private void createMipmaps(GL10 gl, Bitmap bitmap) { gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); width = bitmap.getWidth(); height = bitmap.getHeight();

setFilters(GL10.GL_LINEAR_MIPMAP_NEAREST, GL10.GL_LINEAR);

int level = 0; int newWidth = width; int newHeight = height; while (true) {

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0); newWidth = newWidth / 2; newHeight = newHeight / 2; if (newWidth <= 0) break;

Bitmap newBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig()); Canvas canvas = new Canvas(newBitmap); canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), new Rect(0, 0, newWidth, newHeight), null); bitmap.recycle(); bitmap = newBitmap; level++;

gl.glBindTexture(GL10.GL_TEXTURE_2D, 0); bitmap.recycle();

The createMipmaps() method is pretty straightforward. We start off by binding the texture so that we can manipulate its attributes. The first thing we do is to keep track of the bitmap's width and height and set the filters. Note that we use GL_LINEAR_MIPMAP_NEAREST for the minification filter. If we don't use that filter mipmapping will not work, and OpenGL ES will fall back to normal filtering, only using the base image.

The while loop is straightforward. We upload the current bitmap as the image for the current level. We start at level 0, the base level with the original image. Once the image for the current level is uploaded we create a smaller version of it, dividing its width and height by 2. If the new width is less than or equal to zero we can break out of the infinite loop as we have uploaded an image for each mipmap level (the last image has a size of 1! 1 pixels). We use the Canvas class to resize the image and store the result in newBitmap. We then recycle the old bitmap so we clean up any memory it used and set the newBitmap as the current bitmap. This process is repeated until the image is smaller than 1 ! 1 pixels.

Finally we unbind the texture and recycle the last bitmap that got created in the loop.

setFilters(minFilter, magFilter);

glGraphics.getGL().glBindTexture(GL10.GL_TEXTURE_2D, 0);

public void setFilters(int minFilter, int magFilter) { this.minFilter = minFilter; this.magFilter = magFilter; GL10 gl = glGraphics.getGL();

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilter);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter);

public void bind() {

GL10 gl = glGraphics.getGL(); gl.glBindTexture(GL10.GL_TEXTURE_2D, textureld);

public void dispose() {

GL10 gl = glGraphics.getGL(); gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); int[] textureIds = { textureId }; gl.glDeleteTextures(1, textureIds, 0);

The rest of the class is the same as in the previous version. The only difference in usage is how we call the constructor. And since that is perfectly simple, we won't write an example just for mipmapping. We'll use mipmapping on all our textures used for 3D objects. In 2D mipmapping has less use. A few final notes on mipmapping:

Mipmapping can increase performance quite a bit if the objects you draw using a mipmapped texture are small. The reason for this is that the GPU has to fetch fewer texels from smaller images in the mipmap pyramid. It's therefore wise to always use mipmapped textures on object that might get small.

A mipmapped texture takes up 33% more memory than an equivalent non-mipmapped version. This trade-off is usually fine.

Mipmapping works only with square textures in OpenGL ES 1.x. This is crucial to remember. If your objects stay white even though they are textured with a nice image you can be pretty sure that you forgot about this limitation.

NOTE Once again, because this is really important. Mipmapping will only work with square textures! A 512x256 pixel image will not work.

0 0

Responses

  • Camelia
    How to increase mipmapped on android?
    7 years ago

Post a comment