Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
100 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Theory

Now that we sort of get an idea of what texture mapping is, let's try to understand what is happening (in theory). We can split up texture mapping into several steps:

  1. Load up our 2d image into a texture object
  2. Divide our 2d image into an evenly spaced grid
  3. Let the pipeline know which image (ie. texture) we're going to use
  4. Define our vertex, specifying our texture coordinate to apply

This might seem a little complicated, so I'll step through it as much as possible..

Step 1: Load our 2d image into a texture object

This step is nothing really complicated or earth shattering. We simply load our image from either a physical file or from memory. It's important to note that although the newer 3d hardware is more liberal in terms of "weird" texture image sizes, it's generally a good idea to keep them to NxM, where N and M are a power of 2. (ie. 32x32 or 64x128). You might also want to stay with a maximum texture size of 256x256. This number is from a limitation within the Voodoo line (3dFX) of video hardware.

Although there are excellent image loading libraries out there (such as DevIL, etc), I chose to keep things simple for the purposes of this tutorial. With Direct3D8, we can use the D3DX library to load the image resource, and for OpenGL I just stuck with an old-fashioned bitmap loading routine using the glaux library.

Our next step is to then put this image data into a TextureObject that we can use from our graphics pipeline.

//This is for Direct3D. This is an EASY thing to do!
LPDIRECT3DTEXTURE8 lpTexture = NULL;
hr = D3DXCreateTextureFromFile(m_lpD3DDevice,
        strFilename.c_str(), &lpTexture);
//THAT'S IT! We've loaded our image into a texture!


//OpenGL isn't QUITE so simple, but it's not that bad. OpenGL doesn't really
//come with a helper library like D3DX, which is where the image loading code
//comes in. We just need to load in the image, then create the texture within
//OpenGL through several steps
GLuint tex;
TextureImage *pImage = LoadImage(strFilename);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, pImage->channels, pImage->sizeX, 
  pImage->sizeY, textureType, GL_UNSIGNED_BYTE, pImage->data);
//That's about it for OpenGL. There's always some 
//tinkering that can be done, but this is basically it.

Step 2: Divide our image into an evenly spaced grid

This step is more for you the programmer than for the 3d engine. Basically, when you load up the image into a texture as done in step 1, the pipeline references your texture via a 2d coordinate system. By dividing up your texture into this coordinate system, it is much easier to specify which section of the texture belongs to which vertex on your 3d surface. The common nomenclature of texture mapping is to use U and V to specify the (X,Y) coordinate within the texture.The U,V pair within a texture only runs from 0.0 to 1.0, since a texture can be virtually any NxN image.

Note that another thing to remember about Direct3D and OpenGL, is that the (0.0f,0.0f) U,V corner is different for the two API's. In OpenGL, the (0.0f, 0.0f) is on the lower-left of the texture grid, whereas in Direct3D, the (0.0f,0.0f) is on the upper-left corner of the texture grid.

OpenGL                                      Direct3D

(0.0f, 1.0f)            (1.0f, 1.0f)      (0.0f, 0.0f)             (1.0f, 0.0f)
      |---------------------|                  |------------------------|
      |                     |                  |                        |
   V  |                     |                V |                        |
      |                     |                  |                        |
      |                     |                  |                        |
      |---------------------|                  |------------------------|
(0.0f, 0.0f)    U       (1.0f, 0.0f)      (0.0f, 1.0f)       U     (1.0f, 1.0f)

Because of this friendly invertedness within the two API's, I decided that we would stick to the OpenGL texture mapping coordinate system when specifying our texture information. In a future tutorial, you'll see why we went this route.

Step 3: Let the pipeline know which texture to use

Now that we've loaded our texture and know which coordinates of the texture to map to our 3d surface, we need to let the pipeline know which texture to actually use.

One important thing to remember is that whenever you switch to a texture (or vertex buffer for that matter) within the graphics pipeline, you are creating overhead and a loss of time while the pipeline makes the switch. Try to minimize this switching as much as possible. When you switch to a new texture, try to render every object in your scene using this texture, before moving to the next one. Select your favorite object sorting algorithm such as qsort or something to organize your objects by texture. Because of the way the 3d pipelines handle their clipping algorithms, it's also a good idea to sort your objects by depth so that you render your scene from front to back.

Moving on, specifying a texture is a trivial thing to do with either API.

//Direct3D
m_lpD3DDevice->SetTexture(0, lpTexture);

//OpenGL
glBindTexture(GL_TEXTURE_2D, tex);

Step 4: Define our texture vertex

We've pretty much moved on to our last step. Here is where we then define our surface within the game world. There's nothing that special that we really need to do. We simply let the pipeline know which texture coordinate to apply to which vertex of our surface. The following code might help explain this in better detail.

//Direct3D using our vertex buffer object created in
//the last tutorial
m_pVertices[m_iVerticesInBuffer].vecPos = pos;
m_pVertices[m_iVerticesInBuffer].dwColor = D3DCOLOR_COLORVALUE(r, g, b, a);
m_pVertices[m_iVerticesInBuffer].tu = tex.x;
m_pVertices[m_iVerticesInBuffer].tv = -tex.y;
//because we are using the OpenGL texture mapping coordinate
//(u,v) specification we need to invert our y-axis (or v coordinate)

//OpenGL using our vertex buffer object created in
//the last tutorial
glColor4f(r, g, b, a);
glTexCoord2f(tex.x, tex.y);
glVertex3f(pos.x, pos.y, pos.z);

Bring it on home Wazoo!

Now that we've covered the basic principles of texture mapping with both OpenGL and Direct3D, it's time to put it in our DLL rendering system! For starters, I decided to create a textureManagerInterface object which would reside within our rendererInterface object. Because of the slightly differing methods of creating/storing a texture within OpenGL and Direct3D8, I then implemented our textureManagerInterface object as OGLTextureManager and D3DTextureManager. Within each Manager object, I just decided to use an STL Map container to store the primary key (our identifier for the texture eg. LOGO) along with the actual texture data. This way we can dynamically add as many textures to the system as we wish, while hopefully keeping the time it takes for our system to find our texture very small. Download the updated library which is included with this article and be sure to check out the modified methods within the vbInterface and textureManagerInterface objects.

//within our winmain.cpp
#define LOGO 1
//...
if(FAILED(hr = m_pRenderer->getTextureInterface()->
    AddTexture("data\\textures\\wazooPresents.bmp", LOGO))){
  return hr;
}

//...
//First set our texture in the graphics pipeline
pInterface->getTextureInterface()->setTexture(LOGO);
//lock down our video memory
if(SUCCEEDED(pInterface->m_pVB->lockVB())){

  //create our lower-left triangle
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, -1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(0.0f, 0.0f));
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, 1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(0.0f, 1.0f));
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, -1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(1.0f, 0.0f));

  //create our upper-right triangle
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, -1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(1.0f, 0.0f));
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, 1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(0.0f, 1.0f));
  pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, 1.0f, -10.0f),
        1.0f, 1.0f, 1.0f, 1.0f, D3DXVECTOR2(1.0f, 1.0f));


  //we're done with our surface, so unlock the video memory
  pInterface->m_pVB->unlockVB();
}

Conclusion

Well that's really about it for this tutorial. There's much more to be said for texture mapping obviously, but this is a good enough start to visualize what's happening within our rendering system DLL.

Enjoy those textures!


Contents
  Introduction
  Conclusion

  Source code
  Printable version
  Discuss this article

The Series
  Part I
  Part II
  Part III
  Part IV