OpenGL Texture Mapping
2D Texture
2D Textures are rectangular arrays of data (e.g., images, patterns) that can be mapped onto the surfaces of a model. The mapped surface could be rectangular or non-rectangular. Each element of the texture array is called a texel (texture element) and can contain a RGB/RGBA value. 2D Texture Coordinates is denoted as (s, t), to differentiate from the (x, y, z) coordinates of the 3D model.
Loading the Texture Image/Pattern
The image or pattern to be used as texture must be provided to the
OpenGL application, in the form of a 2D array of RGB/RGBA values. The
following codes define the storage for the texture image data, and
create a texture based on the image data via glTexImage2D
.
#define IMAGE_ROWS 64 #define IMAGE_COLS 64 GLubyte imageData[IMAGE_ROWS][IMAGE_COLS][3]; // Texture image data array ...... // Need to populate the imageData array with pattern or image ...... glEnable(GL_TEXTURE_2D); // Enable 2D Texture // Create texture based on the imageData glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, IMAGE_COLS, IMAGE_ROWS, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
Texture is a OpenGL state, and could be enabled/disabled via glEnable(GL_TEXTURE_2D)
or glDisabled(GL_TEXTURE_2D)
.
The syntax for glTexImage2D
is as follows:
void glTexImage2D(Glenum target, GLint level, GLint textureInternalFormat,
GLsizei imageWidth, GLsizei imageHeight, GLint imageBorder, GLenum imageFormat,
GLenum imageDataType, const GLvoid *imageData)
// target: Specify the texture target.
// level: level of details. OpenGL supports so-called mipmapping to use a sequence of images at different resolutions.
// Level 0 is the highest resolution image.
// textureInternalFormat: Specifies the color format of the texture,
// e.g., GL_RGB, GL_RGBA, GL_LUMINANCE.
// imageWidth, imageHeight, imageBorder: Width, height and border (0 or 1) of the image data.
// imageFormat: Format of the image data, e.g., GL_RGB, GL, RGBA, GL_LUMINANCE.
// imageDataType: image data type, e.g, GL_UNSIGNED_BYTE.
// imageData: Pointer to image data array.
Mapping Texels to Vertices
The command glTexCoord
can be used to map a texel (of a
texture) to a vertex (of the model). A 2D Texel is expressed in (s, t)
coordinates, whereas the model is expressed in (x, y, z) coordinates.
void glTexCoord[1234][sifd](type s-coord, type t-coord,...) void glTexCoord[1234][sifd]v(type *coords)
The texture coordinates (s, t) are normalized to the range of [0, 1], where (0, 0) is the lower-left corner and (1, 1) is the upper-right corner. For example,
glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); // Lower-left corner of texture glVertex3f(......) glTexCoord2f(0.0f, 1.0f); // Upper-left corner of texture glVertex3f(......) glTexCoord2f(1.0f, 0.0f); // Lower-right corner of texture glVertex3f(......) glTexCoord2f(1.0f, 1.0f); // Upper-right corner of texture glVertex3f(......) glEnd();
Activity 1: A Checkerboard Texture Pattern
Here is a complete example (texture.cpp) that lays a checkerboard texture pattern over a rotating cube.
- Copy and paste the code on your machine and try it out. Pay attention to the texture details.
- Change one texture coordinate for the front face (from 1.0 to 0.0, for example) and observe the effect.
- Next set the same texture coordinate to a value outside the range [0,1] and observe the effect.
Texture Parameters
Additional texture parameters can be specified via glTexParameter
command.
void glTexParameter[if](GLenum target, GLenum name, type value) void glTexParameter[if]v(GLenum target, GLenum name, type *value)
Wrapping
The texture (s, t) coordinates is normalized to the range of [0, 1]. We can use the parameters GL_TEXTURE_WARP_S
and GL_TEXTURE_WARP_T
to specify the wrapping behavior for the s and t values outside the range. The possible values are GL_REPEAT
and GL_CLAMP
(clamped at 0.0 and 1.0). For example,
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Repeat the pattern glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // Clamped to 0.0 or 1.0 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
Magnification & Minification Filters
When a texture is mapped to a surface, each texel may cover multiple pixels (called magnification), or multiple texels may cover one pixel (called minification). A scheme called point sampling, where each pixel is mapped to a point in the texture space using interpolation (GL_NEAREST
),
is simple and fast but can leading to unacceptable visual artifacts
(such as aliasing). A smoother appearance can be obtained by using the
average of a group of texels around the sampled point, i.e., linear
filtering (GL_LINEAR
). Another scheme called mipmapping will be discussed later.
In OpenGL, you can set the filter for magnification and minification independently.
// Nearest Point Sampling - fast but visual artifacts glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // 2x2 linear averaging - slower but smoother glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
In perspective projection, the fast texture interpolating scheme may not handle the distortion caused by the perspective projection. The following command can be used to ask the renderer to produce a better texture image, in the expense of processing speed.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
Using a Bitmap as Texture (Nehe Lesson #6)
To use an external image (BMP, JPEG, GIF, PNG) as texture, you need to find a routine that reads the external image into the texture's image data array, before you can use the image data to build the texture.
Nehe's solution uses GLAUX, which is no longer provided in the Windows SDK. The following source file (loadBMP.cpp) contains a simple routine
void loadBMP(char *filename);that reads a 24-bit uncompressed BMP image file into a texture image array (which must be declared in the calling program).
For example, the following codes uses the above routine to loads a BMP image and build a texture:
#include <malloc.h> ...... // Global Variables unsigned char *imageData; int imageRows = ...; // height of your image int imageCols = ...; // width of your image extern void loadBMP(char *); // Declare external function prototype ...... // In initGL() loadBMP("bmpdata/image.bmp"); glTexImage2D(GL_TEXTURE_2D, 0, 3, imageCols, imageRows, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); glEnable(GL_TEXTURE_2D); // Enable 2D texture if(imageData) free(imageData); // Deallocate memory for texture dataDo not worry, OpenGL still has the texture after you free the memory for your texture image. When calling glTexImage2D, the image data gets copied into the OpenGL system, and possibly into the the video card's texture memory.
Activity 2: Using BMP Images as Texture
In this activity you will lay a BMP image over your rotating cube.- Download a 24-bit BMP image of your choice, or select one of the 24-bit BMP images posted here. If you choose do download your own image, make sure that your image size is a power of 2, and that the format of your image is 24-bit bmp (if not, open your image in Paint and convert it to 24-bit bmp format).
- Lay your BMP image over the rotating cube (use the code snippets above).
Mipmaps
At times, simple point sampling or local linear filtering does not yield acceptable visual result. For example, a far-away object in perspective projection will look strange. In this case, we need to use a value that is averaged over a larger area of the texture.
In mipmapping, we can create a series of texture image at different resolutions. For example, suppose the original image is 64x64 (Level 0), we can create lower resolution images at 32x32, 16x16, 8x8, 4x4, 2x2, 1x1. The highest resolution is refered to as level 0; the next is level 1; and so on.
We can use a single image and ask OpenGL to produce the lower-resolution images via command gluBuild2DMipmaps
(in place of glTexImage2d
).
int gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum imageDataFormat, GLenum imageDataType, const void *imageData)
We can then specify the mipmapping filter is to be used via:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // MAG filter is linear glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); // MIN filter is mipmap
Example (Nehe Lesson #7): Texture Filters
In this example, we create 3 textures with different filters (GL_NEAREST
, GL_LINEAR
,
mipmap) for a rotating cube. We also program 'f' key to switch to the
next filter.
The new pieces of code are:
#define NUM_TEXTURES 3 // Number of textures // Global Variables // Texture GLuint textureIDs[NUM_TEXTURES]; // Array of texture IDs GLuint currentTextureFilter = 0; // Currently-used texture filter void initGL(GLvoid) { ...... // Create 3 textures loadBMP(imageFilename); // Load BMP image glGenTextures(NUM_TEXTURES, textureIDs); // Generate 3 texture IDs // Texture ID 0: Use nearest filter which point-samples the texture with interpolation glBindTexture(GL_TEXTURE_2D, textureIDs[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, 3, imageCols, imageRows, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); // Texture ID 1: Use linear filter which average 2x2 texels for smoother appearance glBindTexture(GL_TEXTURE_2D, textureIDs[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, imageCols, imageRows, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); // Texture ID 2: Use mipmapping (for minification) which creates textures at lower resolutions glBindTexture(GL_TEXTURE_2D, textureIDs[2]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); gluBuild2DMipmaps(GL_TEXTURE_2D, 3, imageCols, imageRows, GL_RGB, GL_UNSIGNED_BYTE, imageData); glEnable(GL_TEXTURE_2D); // Enable 2D texture glBindTexture(GL_TEXTURE_2D, textureIDs[currentTextureFilter]); // Select the texture // Correct texture distortion in perpective projection glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); } // Handler for key event void keyboard(unsigned char key, int x, int y) { switch (key) { case 27: // ESC key: exit the program exit(0); break; case 'f': // 'f' key: Select the next texture filter currentTextureFilter++; if (currentTextureFilter >= NUM_TEXTURES) currentTextureFilter = 0; glBindTexture(GL_TEXTURE_2D, textureIDs[currentTextureFilter]); // Select the texture break; default: break; } }
Explanation:
In this example, 3 textures with different filter (GL_NEAREST
, GL_LINEAR
and mipmap) are created, using the same image data. We first use glGenTextures
to generate 3 texture IDs (or names), and then use glBindTexture
to choose a particular texture ID as the current texture. We program 'f' key to switch to the next texture ID. glTexImage2D
is used for creating GL_NEAREST
and GL_LINEAR
filter, while gluBuild2DMipmaps
is used for mipmap.
Activity 3: Switching Between 3 Texture Filters
- Incorporate the code snippets above into a complete program.
- Switch on the fly between different filters and observe the difference.
- Use glRasterPos2f and glutBitmapCharacter to display the current filter index in your window.