Introduction
Texturing is the way of covering objects with textures. In game design sometimes it is needed to have walls with the exact appearance of the real walls, to accomplish that we have two ways, the first way is to design a model for a wall and build the detailed bricks and tiles, the other way is to just build a simple rectangle which represents a face of the wall and cover it with an image containing the wall detailed graphics. The second way is our approach in this tutorial, because it saves time for the programmer and let him do roads, grass fields, walls, sky, etc. with a few lines of code. In this tutorial we are going to build a small cube and cover it with textures.
Texturing in OpenGL
Texturing takes time in coding since it needs long processes and calculations, especially if the image is of compact formats like jpg, gif. However, texturing in OpenGL takes few lines of code, texturing is done in the following stages
- Loading the image, loading the image from the file on disk.
- Creating a texture from the image, in this stage we will define the filters which will be applied on the texture.
- Covering the model with texture, in this phase we will define the coordinates of the model and the texture, then the texture will be mapped to the model.
Loading the Texture and defining filters
The image file will be opened then it will be assigned as a texture. The previous lesson’s code will be used (it is a rotating cube in 3D) and we are going to load a texture to the faces of the cube. See the following code segment.
#include "stdafx.h" # pragma comment (lib, "glaux.lib") #include <GL/glut.h> #include <stdlib.h> #include <glglaux.h> #pragma comment (lib,"opengl32.lib") #pragma comment (lib,"glu32.lib") #pragma comment (lib,"glut32.lib") GLuint ID_1; void LoadTexture(char *FileName , GLuint *ID) { FILE *File=NULL; File=fopen(FileName,"r"); if (File) { fclose(File); } AUX_RGBImageRec *TextureImage=new(AUX_RGBImageRec) ; memset(TextureImage,0,sizeof(void *)*1); TextureImage = auxDIBImageLoad(FileName) ; glGenTextures(1, ID); glBindTexture(GL_TEXTURE_2D, *ID); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data); free(TextureImage->data); free(TextureImage); }
In the above code we have added the glaux library which will help us loading the texture. Also the #include <stdlib.h> for the “free” function to work properly.
First we opened the file, and then we have defined a pointer point to an object of type AUX_RGBImageRec
typedef struct _AUX_RGBImageRec { GLint sizeX, sizeY; unsigned char *data; } AUX_RGBImageRec;
This structure contains the length and the height of the image that we are going to load. Notice that the image must be of type BMP for this structure to work properly also it needs to by of size 2 powered ( 256×256 or 512×512 or 1024×1024 etc.) in this tutorial we have used an image of size 256×256 . Now TextureImage->sizeX is the width of the image and TextureImage->sizeY is the length of the image.
The function glGenTextures tells OpenGL to allocate space for number of 1 texture with its ID. Suppose we have two textures as follow
glGenTextures(1, ID); glGenTextures(1, ID2);
Now if we want to update the first texture, we use
glBindTexture(GL_TEXTURE_2D,*ID);
For the second texture we use
glBindTexture(GL_TEXTURE_2D,*ID2);
For filters we have two types of filters GL_TEXTURE_MAG_FILTER,GL_NEAREST, GL_TEXTURE_MIN_FILTER,GL_NEAREST Which has lower quality, and GL_TEXTURE_MAG_FILTER,GL_LINEAR, GL_TEXTURE_MIN_FILTER,GL_LINEAR which has higher quality.
The function glTexImage2D loads the image into the variable ID, so the variable ID holds the position of all the data of the image. So TextureImage is no more needed so we delete it from the memory by using.
free(TextureImage->data); free(TextureImage);
We will talk about the function glTexImage2D in more details, its general structure is
glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
The first parameter will be always GL_TEXTURE_2D, the second parameter will be zero, it is related to some kind of texture called MIPMAPPING, The third parameter refers to the color system of the image (RED, GREEN, BLUE, ALPHA). 1 means RED. 2 means RED and ALPHA. 3 means RGB, 4 means RGBA. The fourth and fifth parameters are the width and height of the image. The sixth parameter is the border of the image we put zero as default. The seventh parameter takes a value from the following list
GL_COLOR_INDEX GL_STENCIL_INDEX GL_DEPTH_COMPONENT GL_RED GL_GREEN GL_BLUE GL_ALPHA GL_RGB GL_RGBA GL_LUMINANCE GL_LUMINANCE_ALPHA
We choose GL_RGB since this is the current format of our image. The eighth parameter is the type of data which will be loaded. We choose GL_UNSIGNED_BYTE.
So far, we have successfully loaded the image into memory, it is time to use it and bind it to the specified object.
Covering the model with the loaded texture
In the above procedures we just loaded the texture into memory, we need to enable texturing in OpenGL by updating the initialization function “init()” to be as
void init() { glClearColor(0.0,0.0,0.0,0.0); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); LoadTexture("texture_bricks.bmp" , &ID_1); glEnable(GL_TEXTURE_2D); //glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); }
We are going to use the image file named “texture_bricks.bmp” saved on the same directory of the application, then we tell OpenGL to enable texture by using the function glEnable.
In the render() function we created a 3D cube then we covered only five sides of the cube, we left one side to see the internal structure of the cube. Now we can cover the texture with the texture as shown in the following code segment.
void render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(-1.5f,0.0f,-10.0f); glRotatef(rot+=0.1f,0,1,0); rot=rot>=360?0:rot; glBindTexture(GL_TEXTURE_2D, ID_1); //Draw your objects here glBegin(GL_QUADS); //glColor3f(0.0f,1.0f,0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f,-1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f,-1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); //glColor3f(1.0f,0.5f,0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,-1.0f); //glColor3f(1.0f,0.0f,0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,-1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,-1.0f, 1.0f); // glColor3f(1.0f,1.0f,0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f,-1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f,-1.0f); //glColor3f(0.0f,0.0f,1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f,-1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,-1.0f, 1.0f); // glColor3f(1.0f,0.0f,1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f,-1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,-1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,-1.0f); glEnd(); glutSwapBuffers(); }
We have used the function glVertext3f which defines the coordinates of the texture, we will explain in the next section how to use this function to define the coordinates of the texture relative to the coordinates of each face of the cube. The output of our program should be as shown in Fig.1
We notice from the figure that the cube rotates around y. Imagine that you need to build a wall from scratch; it will need to be done in days if you will build all the detailed graphics from scratch.
Defining Texture Coordinates
Defining the coordinates is very important when covering the model with the texture, because if it is not known well this will lead to unexpected results, if you look carefully in the above example we notice the image contains a word “Texture Sample” written in blue on the texture. The word is flipped because we just do texturing without really understanding how the coordinates is defined, the coordinates of the texture is defined as shown in Fig.2
We will take one face of the rectangle to cover it with the above texture, suppose the rectangle has the following coordinates as shown Fig.3.
The true texturing is to map the texture coordinate (1,1) to the cubes coordinate (1,1) then map the texture coordinate (1,0) to the cubes coordinate (1,-1), then map the texture coordinate (0,0) to the cubes coordinate (-1,-1), finally map the texture coordinate (0,1) to the cubes coordinate (-1,1).
Previously we have used the following code which has a wrong arrangement and mapping of the texture coordinates to the cube’s coordinate.
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 10.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 10.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,-1.0f, 10.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,-1.0f, 10.0f);
As a result the image appeared flipped when rendered on the cube. The correct order should be
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 10.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 10.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 10.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( -1.0f, 1.0f, 10.0f);
As an exercise try to change the order of the texture coordinates for the remaining faces of the cube.
The Source code
Click here to download the sample code of this tutorial.