[ Nomad Programming, Graphics, and Algorithms Tutorial ] [ Compiler : MS Visual C++ 6.0 ] [ Part 14 - Texturemapping ] [ http://nomad.openglforums.com ] <<------------------------------------------------------>> [ DISCLAIMER ] I will hold no responsibility to whatever happens to you, your computer, your sanity, your pet, or whatever that may happen to your existence for your reading these texts and for the outcome of the various source code(s) given in each tutorial. So in short, read at your own risk! [ INTRODUCTION ] This tutorial will be on how to texturemap polygons with an image (specifically, a .bmp image). So what is texturemapping? Well, texturemapping your polygons give it more realism than just plainly specifying colors to your polygons/scene. An example is a cube. Depending on the image that you use to texturemap the cube, it can be a crate, a gift-wrapped box, or just a plain old brownish cardboard box...;). SideNote: On a previous tutorial (Part 11), I told you guys to avoid glaux.h as much as possible. Ironically, we shall be using that header file in this tutorial to load .bmp files. You see, what this tutorial is aiming for is to teach you texturemapping and not how to load images...;). [ TUTORIAL ] We start by adding to our project everything that we need. They are the header files and the .lib files. Basically, just like when we added gl.h and glu.h (and its corresponding .lib files -- which are opengl32.lib and glu32.lib, respectively), we also add an additional glaux.h with its corresponding .lib file, which is glaux.lib. The glaux.h header file is also referred to as the "auxiliary library" of OpenGL. Here's how: //----8<----[ CODE BEGIN ]--------// #include #pragma comment(lib, "Glaux.lib") //----8<----[ CODE END ]--------// The next thing you need to know is the data type/structure that will hold the bitmap information and data. This is where we use the glaux.h...:). The data type/structure is named AUX_RGBImageRec! The first three letters of AUX_RGBImageRec (which is "AUX") means the data type/structure is from the glaux.h header file. The next three letters ("RGB") means "Red-Green-Blue". As for the remaining "ImageRec", I am not entirely sure, my guess is it stands for "Image Record"?...:). Anyway, we create a pointer variable of type AUX_RGBImageRec like so: //----8<----[ CODE BEGIN ]--------// AUX_RGBImageRec *Bitmap = NULL; //----8<----[ CODE END ]--------// And to load the bitmap file INTO the pointer variable created above, we call the function auxDIBImageLoad(), like so: //----8<----[ CODE BEGIN ]--------// Bitmap = auxDIBImageLoad("filename.bmp"); //----8<----[ CODE END ]--------// Again, since auxDIBImageLoad() comes from glaux.h, it starts with "aux". "DIB" means "Device Independent Bitmap", more on DIB in the next paragraph. As for "ImageLoad", well, it just says the function loads the image (yes, yes, I know, it was fairly obvious...:) ). Now, about Device-Independent Bitmaps (or DIB). DIBs are best thought of as a file format. It first originated way back in version 1.1 of OS/2 (an operating system). MS Windows users first saw the same bitmap format on the release of MS Windows 3.0 (where it came to be known as DIB). DIB can have an extension of either .bmp or (quite rarely) .dib. What is good about DIB is that it is directly supported by the Windows API (something that we don't take advantage of in this tutorial since we use glaux.h to load the bitmap). OK, enough history!...:). Here's a question: What if "filename.bmp" does not exist? Well, this is not good, since auxDIBImageLoad() assumes the file exists, if it does not, then our program will crash (at least that's what happens on my computer, hehe...:) ). The solution is simple (and very self explanatory). Here's the code/solution: //----8<----[ CODE BEGIN ]--------// BOOL BitmapLoad(char* Filename) { AUX_RGBImageRec *Bitmap = NULL; // Pointer To AUX_RGBImageRec FILE *FileHandle = NULL; // File Handle To Use FileHandle = fopen(Filename,"rb"); // Check To See If The File Exists if(FileHandle != NULL) // If File Exists { Bitmap = auxDIBImageLoad(Filename); // Use glaux.h To Load Image } else // Else If Error Loading File { fclose(FileHandle); // Close File Handle return(FALSE); // Return FALSE } if (Bitmap) { if (Bitmap->data) // If There Is Texture Data { free(Bitmap->data); // Free Texture Data } free(Bitmap); // Free Structure } fclose(FileHandle); // Close File Handle return(TRUE); // Return TRUE } //----8<----[ CODE END ]--------// Obviously, what happened above is that a function was created that returns a boolean value, with a TRUE saying the bitmap exists and was loaded into our *Bitmap pointer variable...;). The next step is to let OpenGL generate and give us a unique integer (actually, it is an unsigned integer). This integer is like an ID number that tells OpenGL which bitmap to use (since we can have/load many images at any given time -- usually during initialization). The relevance of this "ID number" will be apparent later when glBindTexture() is discussed...;). The function that generates this "ID number" is glGenTextures(), and it is used like so: //----8<----[ CODE BEGIN ]--------// UINT TextureVariable; glGenTextures(1, // # Of Texture Names/ID To Create &TextureVariable); // Generated Texture ID //----8<----[ CODE END ]--------// The first parameter tells OpenGL how many texture names/ID to create since the second parameter can accept an array (ie, we could place an array of size 2 into the second parameter and put 2 in the first parameter). TextureVariable is probably a global variable. You should name it "imaginatively"...:). So if the texture you are loading is a crate, then name the variable "crate"!...=). The next step, after having OpenGL generate a texture ID for us, we "bind" the texture ID. So why bind? Well, if we don't do this after generating a texture ID, then there is no way that OpenGL can know which texture you loaded goes with which ID...;). Binding the texture is as easy as: //----8<----[ CODE BEGIN ]--------// glBindTexture(GL_TEXTURE_2D, // Bind A 2D Texture TextureVariable); // Bind Using This Texture ID //----8<----[ CODE END ]--------// The next step, after binding our generated texture ID we build mipmaps. So what is a "mipmap"? And why does it sound like a name of a clown? I cannot answer the second question, but to answer the first one, mipmaps (yes, we can have many mipmap...:) ) are many textures of different sizes. For example, when a texturemapped object or polygon moves far away, OpenGL will automatically choose which of the different sizes of textures (mipmaps) to use for the object to look/render as good as it could be. Building a mipmap is easy, just one function! It looks like so: //----8<----[ CODE BEGIN ]--------// gluBuild2DMipmaps(GL_TEXTURE_2D, // Texture Is A 2D Texture 3, // 3 Means "RGB" Texture...=) Bitmap->sizeX, // Bitmap's Width Bitmap->sizeY, // Bitmap's Height GL_RGB, // Bitmap Is RGB Format GL_UNSIGNED_BYTE, // Type Of Data Each Pixel Is Stored Bitmap->data); // Actual Bitmap Data //----8<----[ CODE END ]--------// You will usually use GL_TEXTURE_2D for the first parameter (FYI: there are 1D and 3D textures!). The other parameters are self-explanatory. Note that Bitmap is the same pointer variable we used to load the bitmap information / data...;). So far, we will modify our BitmapLoad() function above to look like so: //----8<----[ CODE BEGIN ]--------// UINT TextureArray[2]; BOOL BitmapLoad(char* Filename, UINT index) { AUX_RGBImageRec *Bitmap = NULL; // Pointer To AUX_RGBImageRec FILE *FileHandle = NULL; // File Handle To Use FileHandle = fopen(Filename,"rb"); // Check To See If The File Exists if(FileHandle != NULL) // If File Exists { Bitmap = auxDIBImageLoad(Filename); // Use glaux.h To Load Image } else // Else If Error Loading File { fclose(FileHandle); // Close File Handle return(FALSE); // Return FALSE } fclose(FileHandle); // Close File Handle glGenTextures(1, // # Of Texture Names/ID To Create &TextureArray[index]); // Generated Texture ID glBindTexture(GL_TEXTURE_2D, // Bind A 2D Texture TextureArray[index]); // Bind Using This Texture ID gluBuild2DMipmaps(GL_TEXTURE_2D, // Texture Is A 2D Texture 3, // 3 Means "RGB" Texture...=) Bitmap->sizeX, // Bitmap's Width Bitmap->sizeY, // Bitmap's Height GL_RGB, // Bitmap Is RGB Format GL_UNSIGNED_BYTE, // Type Of Data Each Pixel Is Stored Bitmap->data); // Actual Bitmap Data if (Bitmap) { if (Bitmap->data) // If There Is Texture Data { free(Bitmap->data); // Free Texture Data } free(Bitmap); // Free Structure } return(TRUE); // Return TRUE } //----8<----[ CODE END ]--------// The global variable TextureArray[2] is just an array that holds all our generated texture IDs. This means, for example, when we run BitmapLoad("filename.bmp",0);, we are telling OpenGL to place our texture ID for filename.bmp into TextureArray[0]...nice trick huh?...;). OK, take a deep breath first before we go on...=). The next thing we have to worry is to tell OpenGL how it should draw our textures (or select our textures from its list of mipmaps). Fortunately, it is simple! Don't believe me? Read the code below: //----8<----[ CODE BEGIN ]--------// glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter GL_TEXTURE_MIN_FILTER, // Select Minification Filter Type GL_NEAREST); // Set To GL_NEAREST (Blocky & Fastest) glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter GL_TEXTURE_MAG_FILTER, // Select Magnification Filter Type GL_LINEAR); // Set To GL_LINEAR (Smoothest & Slowest) //----8<----[ CODE END ]--------// It is (again), fairly self-explanatory. When we use GL_LINEAR, we want OpenGL to draw our texture the smoothest that it could, but the drawback is that it is slower than GL_NEAREST. In the example above, we set GL_TEXTURE_MIN_FILTER to GL_NEAREST. This means we want OpenGL to draw our textures (that is far away from us) the fastest that it could while making it look "nearly" the same as the original texture/image. Here is a table of the possible values that you can plug inside glTexParameteri(); +------------------------+---------------------------+ | 2ND PARAMETER | 3RD PARAMETER | +------------------------+---------------------------+ | GL_TEXTURE_MAG_FILTER | GL_NEAREST | | | GL_LINEAR | +------------------------+---------------------------+ | GL_TEXTURE_MIN_FILTER | GL_NEAREST | | | GL_LINEAR | | | GL_NEAREST_MIPMAP_NEAREST | | | GL_NEAREST_MIPMAP_LINEAR | | | GL_LINEAR_MIPMAP_NEAREST | | | GL_LINEAR_MIPMAP_LINEAR | +------------------------+---------------------------+ Note that there are still other values that could be plugged into glTexParameteri() (specifically, texture wrapping, where we tell OpenGL to clamp or repeat our texture). They will be discussed in a future tutorial, but not here since this tutorial is beginning to be quite long! Don't worry though, you're not missing much not knowing how to clamp and repeat your texture...at least not yet...:). So far, our function that loads our bitmap will look like: //----8<----[ CODE BEGIN ]--------// UINT TextureArray[2]; BOOL BitmapLoad(char* Filename, UINT index) { AUX_RGBImageRec *Bitmap = NULL; // Pointer To AUX_RGBImageRec FILE *FileHandle = NULL; // File Handle To Use FileHandle = fopen(Filename,"rb"); // Check To See If The File Exists if(FileHandle != NULL) // If File Exists { Bitmap = auxDIBImageLoad(Filename); // Use glaux.h To Load Image } else // Else If Error Loading File { fclose(FileHandle); // Close File Handle return(FALSE); // Return FALSE } fclose(FileHandle); // Close File Handle glGenTextures(1, // # Of Texture Names/ID To Create &TextureArray[index]); // Generated Texture ID glBindTexture(GL_TEXTURE_2D, // Bind A 2D Texture TextureArray[index]); // Bind Using This Texture ID gluBuild2DMipmaps(GL_TEXTURE_2D, // Texture Is A 2D Texture 3, // 3 Means "RGB" Texture...=) Bitmap->sizeX, // Bitmap's Width Bitmap->sizeY, // Bitmap's Height GL_RGB, // Bitmap Is RGB Format GL_UNSIGNED_BYTE, // Type Of Data Each Pixel Is Stored Bitmap->data); // Actual Bitmap Data glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter GL_TEXTURE_MIN_FILTER, // Select Minification Filter Type GL_NEAREST); // Set To GL_NEAREST (Blocky & Fastest) glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter GL_TEXTURE_MAG_FILTER, // Select Magnification Filter Type GL_LINEAR); // Set To GL_LINEAR (Smoothest & Slowest) if (Bitmap) { if (Bitmap->data) // If There Is Texture Data { free(Bitmap->data); // Free Texture Data } free(Bitmap); // Free Structure } return(TRUE); // Return TRUE } //----8<----[ CODE END ]--------// Nice, we're done with our image loading function! So now to use it: we simply call our function BitmapLoad() to generate our texture ID (which will be stored in the global variable TextureArray[2]). Note that TextureArray[2] obviously means it can only hold 2 texture ID. Increase the value if you want to load more than 2 textures. In the accompanying source code, the function was called like so: //----8<----[ CODE BEGIN ]--------// // #define TEXTURE_NOMAD 0 // #define TEXTURE_CRATE 1 BitmapLoad("Images\\Image1.bmp", TEXTURE_NOMAD); BitmapLoad("Images\\crate.bmp", TEXTURE_CRATE); //----8<----[ CODE END ]--------// Now, to select which texture to use, we call glBindTexture(), like so: //----8<----[ CODE BEGIN ]--------// glBindTexture(GL_TEXTURE_2D, // Bind/Select A 2D Texture TextureArray[TEXTURE_NOMAD]); // Bind/Select First Texture //----8<----[ CODE END ]--------// The first parameter means we're selecting a 2D texture. The second parameter is the texture ID that we want to use (the one that OpenGL generated for us...;) ). Now to specify our texture coordinates, we use glTexCoord2f(), like so: //----8<----[ CODE BEGIN ]--------// glBegin(GL_QUADS); // Draw A Quadrilateral!...:) glTexCoord2f(0.0f, 0.0f); // Upper Left Of Texture glVertex3f(-1.0f, -1.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); // Lower Left Of Texture glVertex3f(-1.0f, 1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); // Lower Right Of Texture glVertex3f(1.0f, 1.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); // Upper Right Of Texture glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); //----8<----[ CODE END ]--------// Simply put, you give glTexCoord2f() the texture coordinate of the, well, texture! Here's an illustration: (0,0) (1,0) +---------------+ | | | | | My Texture... | | | | | | | +---------------+ (0,1) (1,1) [ Special thanks to Andrew Lim for pointing out my mistake in the texture coordinate. I had originally (and unknowingly) "inverted" the y-axis on the texture coordinate illustration above -- ie, (0,0) was upper left, (0,1) was lower left, and so on -- which was WRONG! The source code has also been updated with the correct information) ] Simple! Try adjusting values for glTexCoord2f() in the accompanying source code and see what happens. Just remember always that the texture coordinates ranges from 0.0f to 1.0f...;). Oh yeah, the bitmaps that you use should ALWAYS have a width and height that is a power of 2 (ie, width and height could be: 32x32, 64x64, 128x128, 256x256, 1024x1024). Note that not all video cards support images that are 1024x1024 in dimension! It is best to keep your image up to 256x256 in size...;). Look at the source code and try running it to see how all of this fits together...=). [ ENDING ] And that's that! Not hard eh? If there is anything that's not clear to you, your mind, or to that part of you that lets you comprehend the things here, you may email me at (according to priority): willietang@hehe.com or willietang@yahoo.com Again, as with previous tutorials, if you are a newbie, this will look overwhelming. But I seriously suggest you have a glance at the accompanying source code. The source code is filled with comments, so it should guide you quite well (I hope...:) ). Part 15 will be about how to load extensions and how to do multitexturing. Unfortunately, Part 15 will not be for everyone. Why? Because not everyone has a video card that supports OpenGL extensions. But for those that do (lucky you), Part 15 will be quite nice...;). Briefly, extensions are what they say they are, extensions to the OpenGL API. They add functionality and stuffs to OpenGL. Multitexturing is just texturemapping 2 or more textures simultaneously on a single polygon. So check out Part 15! [ NOMAD.SOURCE ] Unfortunately, it was very difficult (and very awkward) to place codes here. So it was eventually decided that the source code be downloaded on my website at: http://nomad.openglforums.com Unzip the file and click on gfxOpenGL4.dsw to see the source codes (I AM, of course, assuming you're using Visual C++ 6.0 here...). <<------------------------------------------------------>> [ Written By: Willie Tang ] [ Written On: 01.06.2003 ] [ Updated On: 02.11.2003 ]