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.
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
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)
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
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
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
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
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.
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:
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.
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.
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.
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.
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.
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.
Advanced. We'll go over this in the next lesson, but for now we'll set it
to 0.
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;
}
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
And now let's look at what has actually come of this.
[Show Code]
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
// include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL; // the pointer to the vertex
buffer
// texture declarations
LPDIRECT3DTEXTURE9 texture_1; // our first texture
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
void init_graphics(void); // 3D declarations
struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
DWORD starting_point = GetTickCount();
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render_frame();
// check the 'escape' key
if(KEY_DOWN(VK_ESCAPE))
PostMessage(hWnd, WM_DESTROY, 0, 0);
while ((GetTickCount() - starting_point) < 25);
}
// clean up DirectX and COM
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// create a device class using this information and the info from
the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
init_graphics(); // call the function to initialize the
square
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); //
turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE); // turn
on the z-buffer
return;
}
// this is the function used to render a single frame
void render_frame(void)
{
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0),
1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0),
1.0f, 0);
d3ddev->BeginScene();
// select which vertex format we are using
d3ddev->SetFVF(CUSTOMFVF);
// set the view transform
D3DXMATRIX matView; // the view transform matrix
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3 (0.0f, 8.0f, 25.0f), // the camera position
&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at
position
&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction
d3ddev->SetTransform(D3DTS_VIEW, &matView); //
set the view transform to matView
// set the projection transform
D3DXMATRIX matProjection; // the projection transform
matrix
D3DXMatrixPerspectiveFovLH(&matProjection,
D3DXToRadian(45), // the horizontal field
of view
(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect
ratio
1.0f, // the near view-plane
100.0f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); //
set the projection
// set the world transform
static float index = 0.0f; index+=0.03f; // an ever-increasing float
value
D3DXMATRIX matRotateY; // a matrix to store the rotation
for each triangle
D3DXMatrixRotationY(&matRotateY, index); // the rotation
matrix
d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY)); //
set the world transform
// select the vertex buffer to display
d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));
// set the texture
d3ddev->SetTexture(0, texture_1);
// draw the textured square
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
return;
}
// 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;
}
// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
// load the texture we will use
D3DXCreateTextureFromFile(d3ddev,
L"metal.png",
&texture_1);
// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
{5, 0, -5, 0xffffffff, 1, 0,},
{-5, 0, -5, 0xffffffff, 0, 0,},
{5, 0, 5, 0xffffffff, 1, 1,},
{-5, 0, 5, 0xffffffff, 0, 1,},
};
// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX),
0,
CUSTOMFVF,
D3DPOOL_MANAGED,
&t_buffer,
NULL);
VOID* pVoid; // a void pointer
// lock t_buffer and load the vertices into it
t_buffer->Lock(0, 0, (void**)&pVoid, 0);
memcpy(pVoid, t_vert, sizeof(t_vert));
t_buffer->Unlock();
return;
}
If you run this code (and include the texture) you should see the following square
rotating:

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]
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.
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!
© 2006-2009 DirectXTutorial.com. All Rights Reserved.