DirectXTutorial.com
The Ultimate DirectX Tutorial
Lesson 8:  Adding Textures
Log In
Lesson Overview

Up until now, the objects we have been making have only been in three colors, red, green and blue.  Of course, we did have the various shades in between, but these made the objects look boring and dull.  In order to liven them up a bit, we'll have to apply 2D images to them, giving them more realism.  These images are called textures.

The process of loading an image and placing it on a 3D object is called texture mapping.  You will see why it is called this in a moment.  This lesson will cover how to perform texture mapping, and how to draw 3D objects with a little more realism.

Texture Coordinates

Before you dive into texture mapping, it is important that you understand texture coordinates.  Texture coordinates are not the same as Cartesian coordinates.

To make things simple, texture coordinates do not apply to the pixels in the texture; they apply to how far down or across a texture the point is.  Because of this, the top-left corner is (0, 0), and the bottom-right corner is (1, 1), no matter the shape or size of the texture.

Image 8.1 - Texture Coordinates

Image 8.1 - Texture Coordinates

Each vertex is given an exact location along a texture.  For example, if a vertex was given a texture coordinate of (0.5, 0.5), it would be in the exact center of any texture applied to it:

Image 8.2 - Texture Coordinate (0.5, 0.5)

Image 8.2 - Texture Coordinate (0.5, 0.5)

This is somewhat of a confusing concept when you have only been using Cartesian and 3D coordinates, so let's take a look at a little bit more of a practical example.  Let's say we want to create a square in our 3D world with a square texture drawn on it, like this:

Image 8.3 - A Textured Square

Image 8.3 - A Textured Square

To do this, we position each vertex of our square in one corner of the texture image, like this:

Image 8.4 - Vertices Positioned on Texture Corners

Image 8.4 - Vertices Positioned on Texture Corners

This is a very simple example, where each corner of the square matches perfectly with each corner of the texture.  Let's look at a few examples of more complexity.

In this example, we will see vertices that are not placed on the corners of a texture.  The result is that the triangles only show part of the texture.  Let's look at this.

Image 8.5 - Vertices Positioned Inside Texture Corners

Image 8.5 - Vertices Positioned Inside Texture Corners

What if we exceeded the borders of the texture?  What we would get in this case is a repeating texture, meaning that the texture would repeat itself to get to the vertex that exceed 1 or went below 0.

Image 8.6 - Vertices Positioned Outside Texture Corners

Image 8.6 - Vertices Positioned Outside Texture Corners

This particular texture goes from (0, 0) to (2, 2).  This means there are four copies of the texture positioned seamlessly next to one another.

If you take a close look at Image 8.6, you will notice that it has become grainy and no longer has the quality of the original texture.  This happens when textured primitives are drawn at too small a size.  It is especially noticeable when being animated or when viewed from a long distance.  So it is probably a better idea to use texture sizes like in Image 8.5, where the texture has become pixelated, but not very noticeably so.

You will probably need to do this for some time before you get it down.  I highly recommend finishing the lesson and practicing it until you are quite good.

Setting a Texture

Like many other techniques in Direct3D, creating and using textures is surprisingly simple.  The most complicated part of it is actually the texture coordinates, which you should get lots of practice at before going on to the next lesson.

Before we get to the end-of-lesson exercises, we need to go over the code used to apply textures.

There are three steps to displaying a texture.

1.  Setting Up a New Flexible Vertex Format
2.  Loading the Texture
3.  Setting the Texture
4.  Releasing the Texture

Let's go over each of these steps:


1.  Setting Up a New Flexible Vertex Format

Prior to this, we had two components in our FVF format.  These were position (X, Y, Z) and diffuse color.  We will now add a third component for a texture coordinate.

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

The two variables we added are called U and V.  This is the equivalent of X and Y.  The reason we don't use X and Y is obvious: we already used them.  U and V are the most-used convention for texture coordinates.

We also add the D3DFVF_TEX1 flag to the CUSTOMFVF definition.  This lets Direct3D know we have a texture coordinate in the vertex.

Of course, we then have to update the code defining our graphics.  Here is the code for the vertices of a square.

struct CUSTOMVERTEX t_vert[] =
{
    {1, 0, -1, 0xffffffff, 1, 0,},
    { -1, 0, -1, 0xffffffff, 0, 0,},
    { 1, 0, 1, 0xffffffff, 1, 1,},
    { -1, 0, 1, 0xffffffff, 0, 1,},
};

Here, we have a square two units in length.  The texture we will load in the next step will fit perfectly over the square.  If you look, you will see that each corner of the texture is covered by one of the vertices.


2.  Loading the Texture

Loading textures is very easy.  It requires a simple function called D3DXCreateTextureFromFile().  Here is the prototype:

HRESULT D3DXCreateTextureFromFile(LPDIRECT3DDEVICE9 pDevice,
                                  LPCTSTR pSrcFile,
                                  LPDIRECT3DTEXTURE9* ppTexture);

Let's go over the parameters quickly.

LPDIRECT3DDEVICE9 pDevice,

This is a pointer to a Direct3D device.  Fortunately, we have just the thing!  All we have to do here is plug in our previously used d3ddev pointer.

LPCTSTR pSrcFile,

This is the filename of the texture image file.  You just plug it in with quotes around it, like L"texture.bmp" or whatever the texture filename is.

This only looks in the local directory.  If you want to load a file in a different directory, you can specify the path like this:  L"c:\directory\texture.bmp".

Note that there is a quite a diverse selection of formats for graphics that can be loaded.  You can load any file with these extentions:  .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga.

LPDIRECT3DDTEXTURE9* ppTexture

This parameter is a new one.  It is a pointer to a texture stored in memory.  What we must do is create the pointer (we don't have to initialize it) and plug the pointer into this parameter.  See the example to find out exactly how this is done.

Once we piece the parameters together, we get something like this.  First, somewhere early in the program or in a header file we do this:

LPDIRECT3DTEXTURE9 texture_1;

When we go to initialize DirectX, we load the texture using the D3DXGetTextureFromFile() function, like this:

D3DXCreateTextureFromFile(d3ddev,    // the Direct3D device
                          L"texture.bmp",    // the filename of the texture
                          &texture_1);    // the address of the texture storage

And that's all there is to loading a texture.  If you don't want to load files like this, I'll go over alternatives to loading this way later in the tutorial.


3.  Setting the Texture

If you thought loading the texture was easy, get ready for this one.  This takes one function, with two parameters.  Here is the prototype:

HRESULT SetTexture(DWORD Sampler,
                   LPDIRECT3DTEXTURE9 pTexture);

What this function does exactly is include the specified texture onto any primitive that is then drawn.  Therefore, all primitives drawn after the fucntion is called have the texture.  Of course, if the function is called again, the new texture is displayed.

Now let's look a the parameters.

DWORD Sampler,

Advanced.  We'll go over this in the next lesson, but for now we'll set it to 0.

LPDIRECT3DTEXTURE9 pTexture

This is the same thing we plugged into the D3DXCreateTextureFromFile() function.  In this case, we put &texture_1.  Do the same here.

Here's what it comes out to:

d3ddev->SetTexture(0, texture_1);    // set the texture

4.  Releasing the Texture

Just like the vertex buffer, textures stack up memory and must be released.  The code, like all releases, is simple:

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    texture_1->Release();    // close and release the texture
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}
The Finished Program

Before your build and run this program, you will need to download the texture file itself and place it in the project folder.  I've provided a few textures (displayed below) that you can take and run in the place of the one I use (which I admit is rather boring as an only texture).

Note that the texture needs to be in the project folder itself.  If you select 'Show All Files' in your Solution Explorer (for Visual Studio 2005) you should be able to see it listed.  If you don't, it is in the wrong place and you will not see the texture.  It should look something like this:

Image 8.7 - The Image In the Solution Explorer

Image 8.7 - The Image In the Solution Explorer

And now let's look at what has actually come of this.

[Show Code]

If you run this code (and include the texture) you should see the following square rotating:

Image 8.8 - The Textured Square

Image 8.8 - The Textured Square

Now let's take a look at some other textures you can include (I will be adding more textures shortly):

[Show Available Textures]



Size of Textures

You will probably want to make your own textures.  However, when you do, note that Direct3D will change your texture if its width and height are not powers of 2.  If the images you use as textures are not like this, Direct3D will stretch them until they are, which can make graphics look awkward.

This means that ideally, the width and the height should be either: 2, 4, 8, 16, 32, 64, 128, 256, 512, and so on.

Summary

Textures make a wonderful addition to 3D.  It is really hard to make good games using the diffuse color we've used up to this point, and with textures you can add impeccable detail to your game.

Of course, this is not all there is to textures.  The subject of textures is quite vast, and I could probably build an entire tutorial devoted to it.  Who knows, I may do that some day.

I'll soon bring to you a little more additional information on how to combine textures, how to use diffuse color with textures, how to use advanced effects with them, how to make your own, and more.

For now, I recommend working on these exercises:

1.  Select one of the textures from above and fill the square with it.
2.  Have the square show multiple repetitions of the texture.
3.  Make a cube and texture it with wood.
4.  Take the Hypercraft from the last lesson and texture it so it looks somewhat decent!

And the next lesson awaits!

Next Lesson:  Vertex Lighting

GO! GO! GO!