Loading Textures From A Resource File & Texturing TrianglesWelcome to the 38th NeHe Productions Tutorial. It's been awhile since my last tutorial, so my writing may be a little rusty. That and the fact that I've been up for almost 24 hours working on the code :) So you know how to texture map a quad, and you know how to load bitmap images, tga's, etc. So how the heck do you texture map a Triangle? And what if you want to hide your textures in the .EXE file? The two questions I'm asked on a daily basis will soon be answered, and once you see how easy it is, you'll wonder why you never thought of the solution :) Rather than trying to explain everything in great detail I'm going to include a few screenshots, so you know exactly what it is I'm talking about. I will be using the latest basecode. You can download the code from the main page under the heading "NeHeGL I Basecode" or you can download the code at the end of this tutorial. The first thing we need to do is add the images to the resource file. Alot of you have already figured out how to do this, unfortunately, you miss a few steps along the way and end up with a useless resource file filled with bitmaps that you can't use. Remember, this tutorial was written in Visual C++ 6.0. If you're using anything other than Visual C++, the resource portion of this tutorial won't make sense (especially the screenshots). * Currently you can only use 24 bit BMP images. There is alot of extra code to load 8 bit BMP files. I'd love to hear from anyone that has a tiny / optimized BMP loader. The code I have right now to load 8 and 24 bit BMP's is a mess. Something that uses LoadImage would be nice. Open the project and click INSERT on the main menu. Once the INSERT menu has opened, select RESOURCE. You are now asked what type of resource you wish to import. Select BITMAP and click the IMPORT button. A file selection box will open. Browse to the DATA directory, and highlight all three images (Hold down the CTRL key while selecting each image). Once you have all three selected click the IMPORT button. If You do not see the bitmap files, make sure FILES OF TYPE at the bottom says ALL FILE (*.*). A warning will pop up three times (once for each image you imported). All it's telling you is that the image was imported fine, but the picture can't be viewed or edited because it has more than 256 colors. Nothing to worry about! Once all three images have been imported, a list will be displayed. Each bitmap has been assigned an ID. Each ID starts with IDB_BITMAP and then a number from 1 - 3. If you were lazy, you could leave the ID's and jump to the code. Lucky we're not lazy! Right click each ID, and select PROPERTIES. Rename each ID so that it matches the name of the original bitmap file. See the picture if you're not sure what I mean. Once you are done, select FILE from the main menu and then SAVE ALL because you have just created a new resource file, windows will ask you what you want to call the file. You can save the file with the default filename or you can rename it to lesson38.rc. Once you have decided on a name click SAVE. This is the point that most people make it to. You have a resource file. It's full of Bitmap images and it's been saved to the Hard Drive. To use the images, you need to complete a few more steps. The next thing you need to do is add the resource file to your current project. Select PROJECT from the main menu, ADD TO PROJECT, and then FILES. Select the resource.h file, and the resource file (Lesson38.rc). Hold down control to select more than one file, or add each file individually. The last thing to do is make sure the resource file (Lesson38.rc) was put in the RESOURCE FILES folder. As you can see in the picture above, it was put in the SOURCE FILES folder. Click it with your mouse and drag it down to the RESOURCE FILES folder. Once the resource file has been moved select FILE from the main menu and SAVE ALL. The hard part has been done! ...Way too many pictures :) So now we start on the code! The most important line in the section of code below is #include "resource.h". Without this line, you will get a bunch of undeclared identifier errors when you try to compile the code. The resource.h file declares the objects inside the resource file. So if you want to grab data from IDB_BUTTERFLY1 you had better remember to include the header file! #include <windows.h> // Header File For Windows #include <gl\gl.h> // Header File For The OpenGL32 Library #include <gl\glu.h> // Header File For The GLu32 Library #include <gl\glaux.h> // Header File For The GLaux Library #include "NeHeGL.h" // Header File For NeHeGL #include "resource.h" // Header File For Resource (*IMPORTANT*) #pragma comment( lib, "opengl32.lib" ) // Search For OpenGL32.lib While Linking #pragma comment( lib, "glu32.lib" ) // Search For GLu32.lib While Linking #pragma comment( lib, "glaux.lib" ) // Search For GLaux.lib While Linking #ifndef CDS_FULLSCREEN // CDS_FULLSCREEN Is Not Defined By Some #define CDS_FULLSCREEN 4 // Compilers. By Defining It This Way, #endif // We Can Avoid Errors GL_Window* g_window; Keys* g_keys; The first line below sets aside space for the three textures we're going to make. The structure will be used to hold information about 50 different objects that we'll have moving around the screen. tex will keep track of which texture to use for the object. x is the x-position of the object, y is the y position of the object, z is the objects position on the z-axis, yi will be a random number used to control how fast the object falls. spinz will be used to rotate the object on it's z-axis as it falls, spinzi is another random number used to control how fast the object spins. flap will be used to control the objects wings (more on this later) and fi is a random value that controls how fast the wings flap. We create 50 instances of obj[ ] based on the object structure. // User Defined Variables GLuint texture[3]; // Storage For 3 Textures struct object // Create A Structure Called Object { int tex; // Integer Used To Select Our Texture float x; // X Position float y; // Y Position float z; // Z Position float yi; // Y Increase Speed (Fall Speed) float spinz; // Z Axis Spin float spinzi; // Z Axis Spin Speed float flap; // Flapping Triangles :) float fi; // Flap Direction (Increase Value) }; object obj[50]; // Create 50 Objects Using The Object Structure The bit of code below assigns random startup values to object (obj[ ]) loop. loop can be any value from 0 - 49 (any one of the 50 objects). We start off with a random texture from 0 to 2. This will select a random colored butterfly. We assign a random x position from -17.0f to +17.0f. The starting y position will be 18.0f, which will put the object just above the screen so we can't see it right off the start. The z position is also a random value from -10.0f to -40.0f. The spinzi value is a random value from -1.0f to 1.0f. flap is set to 0.0f (which will be the center position for the wings). Finally, the flap speed (fi) and fall speed (yi) are also given a random value. void SetObject(int loop) // Sets The Initial Value Of Each Object (Random) { obj[loop].tex=rand()%3; // Texture Can Be One Of 3 Textures obj[loop].x=rand()%34-17.0f; // Random x Value From -17.0f To 17.0f obj[loop].y=18.0f; // Set y Position To 18 (Off Top Of Screen) obj[loop].z=-((rand()%30000/1000.0f)+10.0f); // z Is A Random Value From -10.0f To -40.0f obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f; // spinzi Is A Random Value From -1.0f To 1.0f obj[loop].flap=0.0f; // flap Starts Off At 0.0f; obj[loop].fi=0.05f+(rand()%100)/1000.0f; // fi Is A Random Value From 0.05f To 0.15f obj[loop].yi=0.001f+(rand()%1000)/10000.0f; // yi Is A Random Value From 0.001f To 0.101f } Now for the fun part! Loading a bitmap from the resource file and converting it to a texture. hBMP is a pointer to our bitmap file. It will tell our program where to get the data from. BMP is a bitmap structure that we can fill with data from our resource file. We tell our program which ID's to use in the third line of code. We want to load IDB_BUTTEFLY1, IDB_BUTTEFLY2 and IDB_BUTTERFLY3. If you wish to add more images, add the image to the resource file, and add the ID to Texture[ ]. void LoadGLTextures() // Creates Textures From Bitmaps In The Resource File { HBITMAP hBMP; // Handle Of The Bitmap BITMAP BMP; // Bitmap Structure // The ID Of The 3 Bitmap Images We Want To Load From The Resource File byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 }; The line below uses sizeof(Texture) to figure out how many textures we want to build. We have 3 ID's in Texture[ ] so the value will be 3. sizeof(Texture) is also used for the main loop. glGenTextures(sizeof(Texture), &texture[0]); // Generate 3 Textures (sizeof(Texture)=3 ID's) for (int loop=0; loop<sizeof(Texture); loop++) // Loop Through All The ID's (Bitmap Images) { LoadImage takes the following parameters: GetModuleHandle(NULL) - A handle to an instance. MAKEINTRESOURCE(Texture[loop]) - Converts an Integer Value (Texture[loop]) to a resource value (this is the image to load). IMAGE_BITMAP - Tells our program that the resource to load is a bitmap image. The next two parameters (0,0) are the desired height and width of the image in pixels. We want to use the default size so we set both to 0. The last parameter (LR_CREATEDIBSECTION) returns a DIB section bitmap, which is a bitmap without all the color information stored in the data. Exactly what we need. hBMP points to the bitmap data that is loaded by LoadImage( ). hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); Next we check to see if the pointer (hBMP) actually points to data. If you wanted to add error checking, you could check hBMP and pop up a messagebox if there's no data. If data exists, we use GetObject( ) to grab all of the data (sizeof(BMP)) from hBMP and store it in our BMP (bitmap structure). glPixelStorei tells OpenGL that the data is stored in word alignments (4 bytes per pixel). We then bind to our texture, set the filtering to GL_LINEAR_MIPMAP_LINEAR (nice and smooth), and generate the texture. Notice that we use BMP.bmWidth and BMP.bmHeight to get the height and width of the bitmap. We also have to swap the Red and Blue colors using GL_BGR_EXT. The actual resource data is retreived from BMP.bmBits. The last step is to delete the bitmap object freeing all system resources associated with the object. if (hBMP) // Does The Bitmap Exist? { // If So... GetObject(hBMP,sizeof(BMP), &BMP); // Get The Object // hBMP: Handle To Graphics Object // sizeof(BMP): Size Of Buffer For Object Information // Buffer For Object Information glPixelStorei(GL_UNPACK_ALIGNMENT,4); // Pixel Storage Mode (Word Alignment / 4 Bytes) glBindTexture(GL_TEXTURE_2D, texture[loop]); // Bind Our Texture glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // Mipmap Linear Filtering // Generate Mipmapped Texture (3 Bytes, Width, Height And Data From The BMP) gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits); DeleteObject(hBMP); // Delete The Bitmap Object } } } Nothing really fancy in the init code. We add LoadGLTextures() to call the code above. The screen clear color is black. Depth testing is disabled (cheap way to blend). We enable texture mapping, then set up and enable blending. BOOL Initialize (GL_Window* window, Keys* keys) // Any GL Init Code & User Initialiazation Goes Here { g_window = window; g_keys = keys; // Start Of User Initialization LoadGLTextures(); // Load The Textures From Our Resource File glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // Black Background glClearDepth (1.0f); // Depth Buffer Setup glDepthFunc (GL_LEQUAL); // The Type Of Depth Testing (Less Or Equal) glDisable(GL_DEPTH_TEST); // Disable Depth Testing glShadeModel (GL_SMOOTH); // Select Smooth Shading glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Set Perspective Calculations To Most Accurate glEnable(GL_TEXTURE_2D); // Enable Texture Mapping glBlendFunc(GL_ONE,GL_SRC_ALPHA); // Set Blending Mode (Cheap / Quick) glEnable(GL_BLEND); // Enable Blending We need to initialize all 50 objects right off the start so they don't appear in the middle of the screen or all in the same location. The loop below does just that. for (int loop=0; loop<50; loop++) // Loop To Initialize 50 Objects { SetObject(loop); // Call SetObject To Assign New Random Values } return TRUE; // Return TRUE (Initialization Successful) } void Deinitialize (void) // Any User DeInitialization Goes Here { } void Update (DWORD milliseconds) // Perform Motion Updates Here { if (g_keys->keyDown [VK_ESCAPE] == TRUE) // Is ESC Being Pressed? { TerminateApplication (g_window); // Terminate The Program } if (g_keys->keyDown [VK_F1] == TRUE) // Is F1 Being Pressed? { ToggleFullscreen (g_window); // Toggle Fullscreen Mode } } Now for the drawing code. In this section I'll attempt to explain the easiest way to texture map a single image across two triangles. For some reason everyone seems to think it's near impossible to texture an image to a triangle. The truth is, you can texture an image to any shape you want. With very little effort. The image can match the shape or it can be a completely different pattern. It really doesn't matter. First things first... we clear the screen and set up a loop to render all 50 of our butterflies (objects). void Draw (void) // Draw The Scene { glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer for (int loop=0; loop<50; loop++) // Loop Of 50 (Draw 50 Objects) { We call glLoadIdentity( ) to reset the modelview matrix. Then we select the texture that was assigned to our object (obj[loop].tex). We position the butterfly using glTranslatef() then rotate the buttefly 45 degrees on it's X axis. This tilts the butterfly a little more towards the viewer so it doesn't look like a flat 2D object. The final rotation spins the butterfly on it's z-axis which makes it spin as it falls down the screen. glLoadIdentity (); // Reset The Modelview Matrix glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]); // Bind Our Texture glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z); // Position The Object glRotatef(45.0f,1.0f,0.0f,0.0f); // Rotate On The X-Axis glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f); // Spin On The Z-Axis Texturing a triangle is not all that different from texturing a quad. Just because you only have 3 vertices doesn't mean you can't texture a quad to your triangle. The only difference is that you need to be more aware of your texture coordinates. In the code below, we draw the first triangle. We start at the top right corner of an invisible quad. We then move left until we get to the top left corner. From there we go to the bottom left corner. The code below will render the following image: Notice that half the buttefly is rendered on the first triangle. The other half is rendered on the second triangle. The texture coordinates match up with the vertex coordinates and although there are only 3 texture coordinates, it's still enough information to tell OpenGL what portion of the image needs to be mapped to the triangle. glBegin(GL_TRIANGLES); // Begin Drawing Triangles // First Triangle glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); // Point 1 (Top Right) glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap); // Point 2 (Top Left) glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); // Point 3 (Bottom Left) The code below renders the second half of the triangle. Same technique as above, but this time we render from the top right to the bottom left, then over to the bottom right. The second point of the first triangle and the third point of the second triangle move back and forth on the z-axis to create the illusion of flapping. What's really happening is that point is moving from -1.0f to 1.0f and then back, which causes the two triangles to bend in the center where the butterflies body is. If you look at the two pictures you will notice that points 2 and 3 are the tips of the wings. Creates a very nice flapping effect with minimal effort. // Second Triangle glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); // Point 1 (Top Right) glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); // Point 2 (Bottom Left) glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap); // Point 3 (Bottom Right) glEnd(); // Done Drawing Triangles The following bit of code moves the butterfly down the screen by subtracting obj[loop].yi from obj[loop].y. The butterfly spinz value is increased by spinzi (which can be a negative or positive value) and the wings are increased by fi. fi can also be a negative or positive direction depending on the direction we want the wings to flap. obj[loop].y-=obj[loop].yi; // Move Object Down The Screen obj[loop].spinz+=obj[loop].spinzi; // Increase Z Rotation By spinzi obj[loop].flap+=obj[loop].fi; // Increase flap Value By fi After moving the butterfly down the screen, we need to see if it's gone past the bottom of the screen (no longer visible). If it has, we call SetObject(loop) to assign the butterfly a new texture, new fall speed, etc. if (obj[loop].y<-18.0f) // Is Object Off The Screen? { SetObject(loop); // If So, Reassign New Values } To make the wings flap, we check to see if the flap value is greater than or less than 1.0f and -1.0f. If the wing is greater than or less than those values, we change the flap direction by making fi=-fi. So if the wings were going up, and they hit 1.0f, fi will become a negative value which will make the wings go down. Sleep(15) has been added to slow the program down by 15 milliseconds. It ran insanely fast on a friends machine, and I was too lazy to modify the code to take advantage of the timer :) if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f)) // Time To Change Flap Direction? { obj[loop].fi=-obj[loop].fi; // Change Direction By Making fi = -fi } } Sleep(15); // Create A Short Delay (15 Milliseconds) glFlush (); // Flush The GL Rendering Pipeline } I hope you enjoyed the tutorial. Hopefully it makes loading textures from a resource a lot easier to understand, and texturing triangles a snap. I've reread this tutorial about 5 times now, and it seems easy enough, but if you're still having problems, let me know. As always, I want the tutorials to be the best that they can be, so feedback is greatly appreciated! Thanks to everyone for the great support! This site would be nothing without it's visitors!!! Jeff Molofee (NeHe) * DOWNLOAD Visual C++ Code For This Lesson. * DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl ) * DOWNLOAD Lesson 38 - Enhanced (Masking, Sorting, Keyboard - NeHe). |