Using Bitmaps for OpenGL Texture Mapping in Microsoft Visual Studio

by Rachel Grey
© 2002

Scope of this paper

This paper shows one particular way of getting texture mapping into your OpenGL project. All examples given are written in C/C++, do not use MFC, and assume the use of Microsoft Visual Studio Developer as an IDE. The author does not claim to be an expert in texture mapping (quite the contrary) and will probably be unable to answer questions falling outside the scope of this paper. However, if you need to apply textures to your OpenGL objects and can get your textures into bitmap form, this should be of use to you.

This paper assumes you already know the basics of OpenGL and have a project just waiting to have textures added to it. If this is not the case, you may want to consider learning these basics before trying to learn about textures.


Brief Overview of Texture Mapping in OpenGL

There are many reasons, and many ways, to use textures in OpenGL animation. You can have 1-D, 2-D or 3-D textures and apply them to lines, points and polygons (this paper covers the most common case, where a 2-D texture is applied to polygons). Textures are most often used to lend realism to rendered objects by providing a level of detail that would be difficult, maybe computationally intractable, to obtain using geometric shapes and lighting effects alone. In this case the texture is usually "stuck" onto the object like a decal. You can also have textures that depend on the geometrical position of an object rather than the object itself; textures that make an object appear mirrored; and even different textures (mipmaps) that are applied to the same object depending on how close the "camera" or viewpoint is to that object.

How do we get started with all this? OpenGL doesn't know anything about the common image formats (.bmp, .jpg, .gif) you're probably used to. It remains stubbornly more primitive than those, apparently to provide equal hassle to you, the programmer, no matter what image format you're starting with. Instead, it demands to have textures spoon-fed to it in the form of arrays of bytes. This is good for the makers of OpenGL because they won't have to keep updating OpenGL to keep up with future image formats. And once you have an image in byte[] form, OpenGL does have an astounding array of functions and settings to let you do pretty much anything you can imagine. A few of them are covered in this paper.

For a much better overview of texture mapping in OpenGL, turn to Chapter 9 of the Red Book (that's OpenGL Programming Guide, third edition, by Woo, Neider, Davis and Shreiner), which is something you'll want to do anyway at some point. This paper will try to explain the OpenGL calls it refers to, but it is primarily concerned with getting from bitmaps to a usable array of bytes, and applying that array to some polygons.


Getting from .bmp to byte[]

For a Windows program, especially if you want to deploy the executable, you want your bitmap to be stored as a resource in your project file; that way it will get wrapped up in your .exe (or .scr) file during compilation and you won't have to deploy extra files with your executable.

To get a suitable bitmap into your project, first create one in your favorite paint or image-processing program. Its height and width must each be a power of 2, in pixels. Here is an example of a suitable picture, which happens to be of me (I don't spend all my time programming, no, I'm a geek and a gym rat). Rectangular pictures are also okay, as long as each side is a power of 2. I don't actually know what OpenGL will do if you try to give it a picture of inappropriate dimensions--it probably depends on your implementation of OpenGL--but it is guaranteed not to be pretty. Be aware that you will have the opportunity later to stretch your texture over your objects however you like, so squashing your texture to fit it into an appropriate size doesn't mean it has to look squashed on your animated object.


64 x 128 pixels: fine

128 x 128 pixels: fine

200 x 200 pixels: inappropriate

Once you have your bitmap file, go to the Insert menu, choose Resource..., select Bitmap and press the Import... button. Browse to your bitmap file. If you have saved it in 24-bit format, you may get a message saying that Visual Studio won't be able to properly display it. Don't worry, it will show up fine in your animation. Just go to its properties, modify the resource ID to something reasonable, say IDB_MUSCLES for the texture shown above, and trust Microsoft.

Now, to load the resource into a bitmap and then dig the bit array out of it takes some serious Windows twiddling. I've encapsulated all of this into two files, bmtexture.h and bmtexture.cpp, and here they are.

bmtexture.h:

 
const int TF_NONE = 0;
const int TF_BILINEAR = 1;

void LoadBitmapTexture(int id);
void SetTextureFilter(int newfilter);

bmtexture.cpp:

 
#include <windows.h<

#include "gl/gl.h"
#include "gl/glu.h"


#include "bmtexture.h"
#include "resource.h"

#include "scrnsave.h"  //contains hMainInstance

int Filter;


//affects all textures in memory.
void SetTextureFilter(int newfilter) 
{
        if (newfilter >= 0 && newfilter <= 1) 
        {
                Filter = newfilter;     
        }
}


void LoadBitmapTexture(int id) 
{       
        HBITMAP hBmp = NULL;

        hBmp = (HBITMAP) ::LoadImage(hMainInstance, 
                MAKEINTRESOURCE(id), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

        //get info about the bitmap
        BITMAP BM;
        ::GetObject(hBmp, sizeof(BM), &BM);

        //tell OpenGL to ignore padding at ends of rows
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        if (Filter == TF_NONE) 
        {
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        }
        else
        {
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        }

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, BM.bmWidth, BM.bmHeight,
                0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BM.bmBits);

        DeleteObject((HGDIOBJ) hBmp);  //avoid memory leak (Windows)
}

Unless you know a lot about both OpenGL and Windows, you're now thinking say whaaat?. And if you want to get going with textures without learning the details, you can just copy that text into your files and skip to the next section. However, if you want to know what's going on or might need to change something for your project, open a new window so you can keep the code in view and read on.

To start with the obvious, the SetTextureFilter function allows you to set the filtering for your texture to TF_NONE or TF_LINEAR; this will regulate how smooth your texture looks if you have it stretched over your object in a way that might make it look jagged (say, if it's blown way up). It should be called before the LoadBitmapTexture function, since that checks the value of Filter.

LoadBitmapTexture needs a lot more picking apart. First, note that it doesn't return anything, just sets OpenGL to use the texture you tell it to. The int passed into it should be the resource ID of the texture you want to use (say, IDB_MUSCLE). The LoadImage function is a Windows call that loads the bitmap from a resource and returns an HBITMAP (handle to a Windows bitmap object). We then use GetObject to get the actual BITMAP from the HBITMAP. At that point the bitmap itself is ready to be used in the glTexImage2D call near the bottom of the function; it is this function that actually tells OpenGL to use that image as the texture until otherwise noted. The parameter BM.bmBits at the end of that line is a pointer to the byte array we worked so hard to dig out.

Before that call is made, some OpenGL twiddling has to take place. As the comment says, glPixelStorei tells OpenGL to ignore any padding at the end of the rows of the bitmap. The next two lines, both calls to glTexParameterf, tell OpenGL that we intend to use a 2D texture and we want to tile, or wrap, that image in both the x and the y dimensions if the surface we're mapping it to is larger than a single instance of the texture image. Notice the WRAP_S and WRAP_T terminology; s and t are used instead of x and y for coordinates within the texture. It's your job to come up with some mapping between x and y (and maybe z), and s and t (and maybe r). Similarly, a texture pixel is called a texel. This is all primarily just to avoid confusion between texture coordinates and image coordinates.

The next two lines, no matter which type of filter you're using, are calls to glTexParameterf. These calls tell OpenGL to use the specified filter for both magnification (blowing up) and minification (shrinking) of your texture, as needed. GL_NEAREST will simply use the texel with coordinates nearest to the center of the pixel being drawn, which can result in jagged edges and aliasing. GL_LINEAR will make OpenGL use a weighted linear average of the 2x2 array of texels that lie nearest to the center of the pixel being drawn, which results in a smoother picture but takes longer to calculate.

The important part of the glTexEnvf call is the last parameter, which can be GL_DECAL, GL_REPLACE, GL_MODULATE or GL_BLEND, to specify how texture values are to be combined with the color values of the fragment being processed. GL_MODULATE allows lighting effects to modulate the colors created by the texture, while GL_DECAL and GL_REPLACE don't. GL_BLEND, if I recall correctly, allows the underlying material color to have some effect. Again, refer to the Red Book and your own experimentation for detail.

Finally, we call glTexImage2D to set our bitmap as the texture du jour. Ah, the joy! We have specified a texture. Now we just have to use it.


Three ways to use your texture


OpenGL is stateful, meaning that things tend to stay in the state you set them in until you say otherwise. With texture mapping, this means that OpenGL will use the texture you tell it to for all polygons it draws until you tell it otherwise. Before we can use textures, though, we have to enable them using glEnable, which should be familiar to you from your prior work with OpenGL, and we have to specify exactly how we intend to stretch our texture over our polygons (thus mapping from the s,t,r coordinate system to the x,y,z one).

Manually specified texture coordinates

One way to do your mapping is to specify exactly what texture point you want corresponding to each polygon point. You do this by making a glTexCoord* call for every single glVertex* call you make. This can be more work than using automatic texture coordinate generation (as described in the next subsection) but is useful when you have a flat surface consisting of more than one polygon and you want the texture to stretch smoothly across the whole surface. Automatic coordinate generation is great, but it will start applying the texture afresh with every polygon whether they're adjacent or not.

Here's a code snippet that draws a 40x40 square and applies the IDB_MUSCLES texture to that square. Note that OpenGL considers any texture to be of size 1x1, with coordinate (0, 0) being at the lower left corner of the texture and coordinate (1, 1) being in the upper right. s extends in the x direction and t extends in the y direction.

 

        //enable textures

        glEnable(GL_TEXTURE_2D);
        SetTextureFilter(TF_NONE);
        LoadBitmapTexture(IDB_MUSCLES);

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
        //use GL_MODULATE instead of GL_REPLACE if lighting is being used

        //draw a square with specified texture coordinates

        float xvals[] = {0.0, 0.0, 40.0, 40.0};
        float yvals[] = {40.0, 0.0, 0.0, 40.0};

        float svals[] = {0, 0, 1, 1};
        float tvals[] = {1, 0, 0, 1};

        glPolygonMode(GL_FRONT_AND_BACK, GL_POLYGON);
        glBegin(GL_POLYGON);
                for (int i=0; i < 4; i++)
                {
                        glVertex2f(xvals[i], yvals[i]);
                        glTexCoord2f(svals[i], tvals[i]);
                }
        glEnd();


Automatically generated texture coordinates

Letting OpenGL take care of your texture coordinates is fine in many circumstances, especially if you don't have coplanar, adjacent polygons that must blend smoothly into one another. It saves you quite a bit of code in the form of glTexCoord* calls and can also give you a quick idea of how a texture might look before you go to the trouble of fine-tuning your texture mapping.

To have OpenGL generate your texture coordinates, you must supply it with vectors of parameters telling it how to get the s and t parameters from x and y. The vectors, as exemplified by myparamsS and myparamsT in the code below, tell OpenGL what to multiply by the x and y parameters to come up with the texel coordinate. Because in this case I knew I'd be drawing a 40x40 square, I made the appropriate factors equal to the value 1/40 in the code below (the size of one side of the texture divided by the size of one side of the square). Most of the time you won't have this luxury and will just have to find a size for your texture that looks pretty good; but most of the time you will probably have vectors of the form I show below, with the parameter corresponding to x (in the s-defining vector) and the one corresponding to y (in the t-defining vector) being nonzero, the other spatial parameters being zero. The last one corresponds to alpha values, which as a beginning OpenGL programmer you probably will not be concerned with. Just leave it set to 1.0.

Here is the code snippet showing automatic texture coordinate generation to place the IDB_MUSCLE texture on a square. Tiling is turned on, but in this case the image is tiled exactly once, so it doesn't matter.

 

        //enable textures and set up automatic texture coordinate generation

        glEnable(GL_TEXTURE_2D);
        SetTextureFilter(TF_NONE);
        LoadBitmapTexture(IDB_MUSCLES);
        
        float oneFortieth = 1.0/40.0;

        //define how the s parameter depends on x, y, z, w
        GLfloat myparamsS[] = {oneFortieth, 0.00, 0.00, 1.0};

        //define how the t parameter depends on x, y, z, w
        GLfloat myparamsT[] = {0.00, oneFortieth, 0.00, 1.0};
        
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_S, GL_OBJECT_PLANE, myparamsS);
        
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_T, GL_OBJECT_PLANE, myparamsT);

        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
        //use GL_MODULATE instead of GL_REPLACE if lighting is being used

        //draw the square; let OpenGL do the tex coordinates

        float xvals[] = {0.0, 0.0, 40.0, 40.0};
        float yvals[] = {40.0, 0.0, 0.0, 40.0};

        glPolygonMode(GL_FRONT_AND_BACK, GL_POLYGON);
        glBegin(GL_POLYGON);
                for (int i=0; i < 4; i++)
                        glVertex2f(xvals[i], yvals[i]);
                
        glEnd();

The glTexGeni calls tell OpenGL to paint the texture onto the objects drawn (GL_OBJECT_LINEAR) instead of having the chosen texels correspond to distance from the viewpoint, which would result in the texture holding still as the shape moves. This (GL_EYE_LINEAR) is an interesting effect and makes it look as though objects are "swimming through" a texture, but is not the one we want here.

glTexGenfv tells OpenGL to use the parameter vectors we set up earlier, the calls to glEnable enable texture generation for s and t, and the call to glTexEnvf acts as before, replacing the color value entirely with the texel's color value and more or less disabling lighting for the object. That's it! We can now go on to draw several polygons and the texture will be tiled onto all of them.


Environment mapping

Sometimes, you'd like to create an infinitely shiny object that's reflecting everything around it--say, the contents of a room, or just an abstract pattern. This is known as environment mapping, and OpenGL makes it remarkably easy. It's just a variation on the automatic texture generation shown above. This works best for 3D objects that are curved in interesting ways, and works less well for objects with large flat areas (which will reflect only a few pixels of your environment image).

Here's a code snippet showing how to set up environment mapping:

 
        SetTextureFilter(TF_LINEAR);

        textureId = LoadBitmapTexture(IDB_WHATEVER);
        
        glEnable(GL_TEXTURE_2D);

        //environment mapping
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);


Other resources

Some other resources you may find useful include:

Good luck and have fun with your textures. Please let me know if you find errors in this document, or found any part of it especially misleading/unhelpful, so I can change it. (You could also drop me a line if it actually helped you.) I'm no texture expert and this document may change if I find errors or if I ever learn more about this ridiculously complex topic.


Rachel Grey
lemming@alum.mit.edu
City in the Rain