It's difficult to make a spectacular game without textures. Fortunately, meshes
include the ability to have textures attached to the .x file, so you don't have
to manually add each texture.
Unfortunately, however, you do need to add some code of your own, and this lesson
will cover how to do that.
In this lesson, we are going to use the model "airplane
2.x". This airplane,
if run using the code from the previous lesson, will show something like this:

Image 3.1 - The Airplane 2, Untextured
However, if you view this particular mesh using the mesh viewer provided with DirectX,
you will find that it actually looks quite different. It looks like this:

Image 3.2 - The Airplane 2, Textured
This means that our code, so far, does not actually show textures when they are
available. For this we will need to enter additional code.
There are two simple steps to adding this code:
1. Load the textures while loading the mesh.
2. Set the subset's texture before each subset is drawn.
Let's look into each of these in detail.
To load a texture, what we first need to do is get the filename of the texture.
This is loaded when the mesh is loaded, and stuck away in the material buffer.
Our task will be to extract it, read it, and load the texture.
Here is the code that handles the material information once the mesh is loaded.
Added in and in bold is the new code that handles the texture.
D3DMATERIAL9* materal; // a pointer to a material buffer
LPDIRECT3DTEXTURE9* texture; // a pointer to a texture
// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();
// create a new material buffer and texture for each material in
the mesh
material = new D3DMATERIAL9[numMaterials];
texture = new LPDIRECT3DTEXTURE9[numMaterials];
for(DWORD i = 0; i < numMaterials; i++) // for each material...
{
material[i] = tempMaterials[i].MatD3D; // get the material
info...
material[i].Ambient = material[i].Diffuse; // and make
ambient the same as diffuse
USES_CONVERSION; // allows certain string conversions
// if there is a texture to load, load it
if(FAILED(D3DXCreateTextureFromFile(d3ddev,
CA2W(tempMaterials[i].pTextureFilename),
&texture[i])))
texture[i] = NULL; // if there is no texture,
set the texture to NULL
}
Well, about half of this is devoted to texture loading and the other half to string
management, but I will go over it all at the same time.
This the pointer to the textures, as we will see in the next command. It is
placed in the beginning of your program, along with the *material declaration, so
it can be global.
Each material has its own buffer, if it has any at all. If you look at the
Airplane 2, you will find that there are a few subsets with no textures,
but we'll get to that later. Whether or not the subset has a texture, we will
set aside a slot in this texture array. This line just creates a blank texture
for each subset.
This is a macro that enables us to use certain string conversions. In the
next line, we will need to convert a LPSTR to an LPCWSTR. This macro enables
the macro we will use to do this.
Note that using this macro requires a new header file called "atlbase.h".
We will add this in the finished program for this lesson.
This checks whether or not a texture name was available for the subset. As
mentioned, not all subsets have textures. If there is no texture, there will
be no filename. If this happens, the function D3DXCreateTextureFromFile()
will return a failure. If this is the case, we go on to the next line,
texture[i] = NULL; in which case we will be able to detect it later.
More on this in the next step.
We've seen this one before. It simply loads the texture from the file whose
name is provided.
The first parameter is simply the Direct3D Device, d3ddev.
The second parameter is the filename. The filename for the appropriate subset
is located in tempMaterials[i].pTextureFilename. However, this filename is
in an LPSTR format. In Visual C++ 2005, this string needs to be in an LPCWSTR
format. We can't unfortunately, cast it normally. We will have to use
a macro called CA2W to convert it. Therefore we get the line for this parameter
as:
CA2W(tempMaterials[i].pTextureFilename),
This gets the string holding the filename of the texture, converts it to a wide
string, and uses it to load the texture.
The third parameter simply holds the texture pointer in which to place the texture.
This last line is only run if there is no texture, in which case we simply initialize
the texture to NULL. We'll see why this is important in the next step.
The code for setting the texture should be quite familiar to you, and is nowhere
near as tedious as loading it. Here is the code for displaying the model,
with the texture code in bold.
// draw the spaceship
for(DWORD i = 0; i < numMaterials; i++) // loop through each subset
{
d3ddev->SetMaterial(&material[i]); // set the
material for the subset
if(texture[i] != NULL) // if the subset has a texture
(if texture is not NULL)
d3ddev->SetTexture(0, texture[i]); //
...then set the texture
meshSpaceship->DrawSubset(i); // draw the subset
}
This part is simple. All we do is check to see if there is a texture.
If there was a texture, we set it to NULL (back in step 1). If it isn't NULL,
then we go on and set the texture. I'm not going to go over each line because
there are two simple lines and we've gone over them before.
That's all there is to loading textured meshes.
Now let's take a look at the whole program.
This program uses the 3D model "airplane 2.x", which must be accompanied by the
two texture files "bihull.bmp" and "wings.bmp".
[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>
#include <atlbase.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
LPDIRECT3DSURFACE9 z_buffer = NULL; // the pointer to the z-buffer
// mesh declarations
LPD3DXMESH meshSpaceship; // define the mesh pointer
D3DMATERIAL9* material; // define the material object
LPDIRECT3DTEXTURE9* texture; // a
pointer to a texture
DWORD numMaterials; // stores the number of materials in the mesh
// 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
void init_light(void); // sets up the light and the material
// 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"WindowClass1";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL,
L"WindowClass1",
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; // automatically
run the z-buffer for us
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // 16-bit
pixel format for the z-buffer
// 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);
// create the z-buffer
d3ddev->CreateDepthStencilSurface(SCREEN_WIDTH,
SCREEN_HEIGHT,
D3DFMT_D16,
D3DMULTISAMPLE_NONE,
0,
TRUE,
&z_buffer,
NULL);
init_graphics(); // call the function to initialize the
triangle
init_light(); // call the function to initialize the
light and material
d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE); // turn
on the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE); // turn
on the z-buffer
d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));
// ambient light
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();
// SET UP THE TRANSFORMS
D3DXMATRIX matView; // the view transform matrix
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3 (0.0f, 8.0f, 16.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
D3DXMATRIX matProjection; // the projection transform
matrix
D3DXMatrixPerspectiveFovLH(&matProjection,
D3DXToRadian(45), // the horizontal field
of view
SCREEN_WIDTH / SCREEN_HEIGHT, // the aspect
ratio
1.0f, // the near view-plane
100.0f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);
// set the projection
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
// draw the spaceship
for(DWORD i = 0; i < numMaterials; i++) // loop through
each subset
{
d3ddev->SetMaterial(&material[i]);
// set the material for the subset
if(texture[i] != NULL) // if the subset
has a texture (if texture is not NULL)
d3ddev->SetTexture(0, texture[i]);
// ...then set the texture
meshSpaceship->DrawSubset(i); // draw
the subset
}
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
return;
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
meshSpaceship->Release(); // close and release the spaceship mesh
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)
{
LPD3DXBUFFER bufShipMaterial;
D3DXLoadMeshFromX(L"airplane 2.x", // load this file
D3DXMESH_SYSTEMMEM,
// load the mesh into system memory
d3ddev,
// the Direct3D Device
NULL,
// we aren't using adjacency
&bufShipMaterial,
// put the materials here
NULL,
// we aren't using effect instances
&numMaterials,
// the number of materials in this model
&meshSpaceship);
// put the mesh here
// retrieve the pointer to the buffer containing the material
information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();
// create a new material buffer and texture for each
material in the mesh
material = new D3DMATERIAL9[numMaterials];
texture = new LPDIRECT3DTEXTURE9[numMaterials];
for(DWORD i = 0; i < numMaterials; i++) // for each
material...
{
material[i] = tempMaterials[i].MatD3D;
// get the material info
material[i].Ambient = material[i].Diffuse;
// make ambient the same as diffuse
USES_CONVERSION; // allows certain
string conversions
// if there is a texture to load, load it
if(FAILED(D3DXCreateTextureFromFile(d3ddev,
CA2W(tempMaterials[i].pTextureFilename),
&texture[i])))
texture[i] = NULL; // if
there is no texture, set the texture to NULL
}
return;
}
// this is the function that sets up the lights and materials
void init_light(void)
{
D3DLIGHT9 light; // create the light struct
ZeroMemory(&light, sizeof(light)); // clear out the
struct for use
light.Type = D3DLIGHT_DIRECTIONAL; // make the light
type 'directional light'
light.Diffuse.r = 0.5f; // .5 red
light.Diffuse.g = 0.5f; // .5 green
light.Diffuse.b = 0.5f; // .5 blue
light.Diffuse.a = 1.0f; // full alpha (we'll get to that
soon)
D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};
// the direction of the light
light.Direction = vecDirection; // set the direction
d3ddev->SetLight(0, &light); // send the light
struct properties to light #0
d3ddev->LightEnable(0, TRUE); // turn on light #0
return;
}
If you run this, you should get something like this:

Image 2.3 - The Biplane In Action
All Right! Now we can load up textured models and display them! With a higher quality of 3D graphics under your belt, it's time to get to work.
Next, we'll go into animated models, as well as models created by your game (rather
than an external 3D modeler).
While I build those lessons, do these exercises, and get very good using meshes.
1. Find another textured model and load it.
2. Get both the airplane and the other model on the screen at the same time.
3. Make the propeller spin.
4. Make your own texture to use on this model.
Next Tutorial: Game Display
GO! GO! GO!
© 2006-2009 DirectXTutorial.com. All Rights Reserved.