![]()
::[ PART 14 - TEXTUREMAPPING
]::[ Source
]::[ Printable Version
]::
::[
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!
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...;).
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 <gl/glaux.h>
#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); // GL_NEAREST (Blocky & Fastest)
glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter
GL_TEXTURE_MAG_FILTER, // Select Magnification Filter Type
GL_LINEAR); // 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); // GL_NEAREST (Blocky & Fastest)
glTexParameteri(GL_TEXTURE_2D, // Set 2D Texture Parameter
GL_TEXTURE_MAG_FILTER, // Select Magnification Filter Type
GL_LINEAR); // 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(1.0f, 0.0f); // Lower Right Of Texture
glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); // Upper Right Of Texture
glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); // Upper Left Of Texture
glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); // Lower Left 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,1) (1,1)
+---------------+
| |
| |
| My Texture... |
| |
| |
| |
+---------------+
(0,0) (1,0)
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...=).
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!
|
|