This lab is an introduction to Texture Mapping. It is organized by the following sections:
After the lab lecture, you have until next week to:
Online OpenGL Manual
More on texture mapping.
The following diagram represents the idea of texture mapping:
The idea is that you have multiple images to cover multiple levels of detail. For instance, for an object far away, you have a texture or a mipmap that is small and with little detail, and for closer objects, you have a texture that is large and detailed.
Taking an example from msdn (Search for EasyTex). If you are looking at a stop sign from far away, you see only a red circle. As you get closer, you can see it is a red shape with some letters. Finally, you see the entire stop sign. You might specify the mipmaps to look something like this:
If you did not specify different levels of detail, OpenGL would try and squish the large stop sign into a smaller image. It would combine the red sign with the white letters, and you would get a pink blob instead of a round, red circle.
The Steps in Texture Mapping are the following
To begin with, we need a texture name. This is actually a number that OpenGL uses to identify or index all of the textures. Any unsigned integer may be used as a texture name, but using glGenTextures() is recommended to avoid accidentally reusing names. glGenTextures() generates unused texture names. The code is the following:
GLuint texName //allocate a texture name glGenTextures(1, &texName);
We now have a texture name (or unique index), now we want to specify what texture we are working with. There are two forms of textures in OpenGL, 1D and 2D. In this lab, we will be working with 2D, this is reflected in the GL_TEXTURE_2D in the following code:
//select our current texture glBindTexture(GL_TEXTURE_2D, texName);
By issuing the glBindTexture() for the first time with texName, we are creating a texture object which is 2D and contains some initial default values or properties.
You will notice that we will call glBindTexture() again later with texName. When glBindTexture() is called with a previously created texture object, that texture object becomes active. Therefore, the next call to glBindTexture() will make texName the active or current texture state.
We've got a texture name and we've created a texture with the glBindTexture() command, now we can specify some properties or parameters for the texture. If we don't specify any parameters, the default values will be used.
The following may be some parameters that we want to set. The comments provide some idea as to what each of these calls is doing. For more details about these parameters, you might want to consult the manual for glTexParameter .
//The texture wraps over the edges (repeats) in the x direction glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //The texture wraps over the edges (repeats in the y direction glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //when the texture area is large, repeat texel nearest center glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //when the texture area is small, repeat texel nearest center glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
Next, we can specify the image we are going to use. Assuming that we have an image (checkImage) which is a 2 dimensional checkerboard pattern which is stored in a three dimension array containing RGBA components. We can set the texture to the checkImage with the following code:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, checkImage);
See the Online notes for glTexImage2D
We've set up the texture, we've specified some parameters, and we've provided a checkerboard image. Now, we need to turn on texture mapping. This is done with a call to glEnable sending it the GL_TEXTURE_2D parameter:
You also need to specify which texture you are going to be working with. You do that through a call to glBindTexture. Remember, the first time we called glBindTexture it created a texture object with default values for the texture image and texture properties. The second time we call it with the texture name, that texture data becomes the current texture state.
This is the last key to texture mapping. We have to specify which part of our texture image are going to fit or map onto the object. Texture coordinates are often referred to as (s and t). For a texture image, s and t are in the range of 0 to 1. The mapping itself is similar to setting a color for each vertex. For texture mapping, you specify a texture coordinate and then you specify the coordinates for a vertex of the object. To map the checkerboard texture onto a square, our code would look like this:
glBegin(GL_QUADS); glTexCoord2f( 0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0); glTexCoord2f( 0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0); glTexCoord2f( 1.0, 1.0); glVertex3f( 0.0, 1.0, 0.0); glTexCoord2f( 1.0, 0.0); glVertex3f( 0.0, -1.0, 0.0); glEnd();
Graphically, we could see the mapping as the following:
In the example that we are using, we have specified that we would like to repeat the pattern, but we are currently not making use of this repeating feature. When we repeat a texture, it provides a "tile" effect. The following code specifies that we will repeat the texture map.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
But in order to see any results, we have to assign texture coordinates outside the range [0,1]. For instance to get a 3x3 tile of the checkerboard, we would specify the coordinates as the following:
glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0); glTexCoord2f(0.0, 3.0); glVertex3f(-2.0, 1.0, 0.0); glTexCoord2f(3.0, 3.0); glVertex3f(0.0, 1.0, 0.0); glTexCoord2f(3.0, 0.0); glVertex3f(0.0, -1.0, 0.0); glEnd();
Try it out!
Graphically, we could see the mapping as the following (the red lines are placed in the texture map to help show that the texture is repeated three times)
Instead of specifying that we want to repeat the texture, we can specify that we want to clamp the texture. In this case, we change the GL_REPEAT to GL_CLAMP.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
What does this mean? Any values greater than 1.0 are set to 1.0, and any values less than 0.0 are set to 0.0. For instance, if we use GL_CLAMP with the above texture coordinates, then a small copy of the texture will appear in the bottom left-hand corner of the object. To the right will be a "clamp" of the texture at s=1. To the top, will be a clamp of t=1.
Graphically, we could see the mapping as something like this (again, the red lines are in the image to point out the original texture in the bottom left-hand corner).
The checkerboard pattern in the above example was a simple black and white image that we stored into a three dimensional array. We may want to use more complicated pictures--for instance, images that we have taken or scanned.
There are several graphics file formats. OpenGL does not come with any image loading utilities, but if we have an image loading library, we can read from them and use them for our texture map. This lab uses FreeImage, an open source cross platform image loading library that supports a wide variety of image types. The library has been included in your exercise as a static library for your convenience.
FreeImage is easy to use, but to make it even easier it has been wrapped by the Image class found in . To use it, simply create an Image object and .open() an image by name. The object will store width (sizeX), height (sizeY) and pixel data (data) for the currently open image. You may use the same object to open any number of images one at a time. See the exercise for an example of its use. If you provided a palettized image, it will be converted to either 24 or 32-bit by the loader based on whether it has transparency.
One interesting quirk of FreeImage is that it stores data in BGR or BGRA byte order, rather than in the RGB or RGBA order that you might expect and that OpenGL uses internally. This byte order is preserved in the Image class's data member. Fortunately, OpenGL provides a compatible image format specifier. You shoule specify GL_BGR for 24-bit images, and GL_BGRA for 32-bit images.
If you prefer not to use a library, or would like to try writing your own texture loader, Windows .BMP format for 24-bit images is pretty easy to figure out since no de-compression or palette lookup tables are involved. This lab used to have a custom BMP loader, which you can see in this old version of this lab's exercise. The notes that follow can help you understand how it works.
Windows bitmap files are stored in a device-independent bitmap (DIB) format that allows Windows to display the bitmap on any type of display device. The term "device independent" means that the bitmap specifies pixel color in a form independent of the method used by a display to represent color. The default filename extension of a Windows DIB file is .BMP.
You can summarize the bitmap as containing four chunks of information or data:
The first bytes in the file contain the header information (1, 2, and 3 in the list above), then subsequent bytes contain the RGB components of each pixel in the image.
To summarize the header information, the first 14 bytes contain the BITMAPFILEHEADER,
which is broken down into:
|2 bytes||type of file which must be BM|
|4 bytes||size of the file|
|2 bytes||reserved1 which must be 0|
|2 bytes||reserved2 which must be 0|
|4 bytes||byte offset from the BITMAPFILEHEADER structure to the actual bitmap data in the file|
The next 40 bytes contain the BITMAPINFOHEADER, which is broken down into:
|4 bytes||number of bytes required by the BITMAPINFOHEADER|
|4 bytes||width of the bitmap in pixels|
|4 bytes||height of the bitmap in pixels|
|2 bytes||number of planes for the target device (must be 1)|
|2 bytes||number of bits per pixel. In our example, it must be 24.|
|4 bytes||type of compression for a compressed bitmap|
|4 bytes||size, in bytes of the image|
|4 bytes||horizontal resolution, in pixels per meter|
|4 bytes||vertical resolution, in pixels per meter|
|4 bytes||number of color indexes in the color table actually used by the bitmap|
|4 bytes||number of color indexes that are considered important for displaying the bitmap|
Next is the color table, for our example, there is no color table because each pixel is represented by 24-bit red-green-blue (RGB) values in the actual bitmap data area.
Finally is the bitmap data area. It consist of a series of BYTE values representing consecutive rows, or "scan lines," of the bitmap. Each scan line consists of consecutive bytes representing the pixels in the scan line, in left-to-right order. The first byte represents the pixel in the lower left-hand corner of the bitmap.
I have explained all this to give you a little more understanding for the code in this lab's exercise. The key thing to know is that most of the header information for the bitmap is skipped. The height and the width of the bitmap are extracted to allow you to load images of varying sizes. Then, the RGB components are stored into a dynamically allocated array (data), which is part of the Image structure. Another thing to note is that .BMP files store their color information in Blue-Green-Red order. For OpenGL, the order is changed to Red-Green-Blue.
Although it would appear that your texture images can be of varying sizes, for maximum compatibility (see Issue #2) you should use images with both the width and height a power of 2. For instance, valid sizes would be: 64 x 16, 256 x 256, 128 x 128, and 16 x 4. Whereas invalid sizes would be: 100 x 100, 6 x 4, 5 x 5, and 2 x 22.
If you do have an odd size image, you can use the gluScaleImage() function in your code. Refer to the Online OpenGL manual.
The following are a list of references which were used in the making of this lab:
Click here for a zipped Visual Studio 2008 Project. You will have to unzip this file and double click on lesson06.sln.
Click here for a zipped XCode Project. You will have to unzip this file and double click on NeHe-Lesson-06.xcodeproj.
Click here for zipped source code and textures for this exercise. You will have to unzip this file and double click on lesson06.sln.
The code for this exercise is provided from http://nehe.gamedev.net (lesson 6). Modify the code by following these steps:
glutTimerFunc(ms_delay, &animate, 1);line in main(). Now hitting the 'a' key will toggle animation. You will also notice that when the window is redrawn (for instance, when it is re-sized), the cube will be rotated.
For a little more on texture mapping, click here.