Loading a Static Mesh (.X Format)

  CU_StaticMesh.zip (57.7 KiB, 5,214 hits)

Hard coding all of the vertex data will get you no where fast. 3D modeling programs can usually export geometry into many different file formats. These formats can hold different types of information and need to be parsed differently. The format we will use is the DirectX .x format. The .x format is the DirectX native 3D mesh file format.

There are 2 different ways to load .x files. If you don’t want or need any animation, you can load the file the easy way. This is what we’ll do in this tutorial. The other method of loading an .x file saves all the animation and bone hierarchy data. This is more complicated and we’ll go over this in a later tutorial. The file we will load in is a simple model I made in Maya and then exported to the .x format using the Maya .x exporter that ships with the DirectX SDK. You can view .x files individually using the DirectX Viewer utility that comes with the SDK.

DirectX Viewer

To use meshes, I created two class: CMesh and CMeshInstance. CMesh takes care of loading and storing a single mesh from an .x file. CMeshInstance is in charge of rendering the mesh by means of a reference to a CMesh object. There are two different classes to save memory. Loading a new mesh for each instance would be pretty expensive. With two classes, we can load the mesh once and then just have all the instances point to that mesh. This will also come in handy with animated meshes.

#include "stdafx.h"
#include "CWorldTransform.h" 

class CMesh
{
public:
    CMesh();
    ~CMesh() { Release(); }
    BOOL Load( LPDIRECT3DDEVICE9 pDevice, char* file );
    void Release(); 

    LPD3DXMESH GetMesh() { return m_pMesh; }
    DWORD GetNumMaterials() { return m_numMaterials; }
    D3DMATERIAL9* GetMeshMaterial( int i ) { return &m_pMeshMaterials[i]; }
    LPDIRECT3DTEXTURE9 GetMeshTexture( int i ) { return m_ppMeshTextures[i]; } 

private:
    LPD3DXMESH m_pMesh;
    DWORD m_numMaterials;
    D3DMATERIAL9 *m_pMeshMaterials;
    LPDIRECT3DTEXTURE9 *m_ppMeshTextures;
};

The CMesh class wraps up an ID3DXMesh interface. Remember, an ID3DXMesh interface is just a container for both a vertex buffer and an index buffer. We’ll also store arrays of D3DMATERIAL9 structures and IDirect3DTexture9 interfaces since these are not stored in the meshes.

#include “..\include\stdafx.h”
#include “..\include\CMesh.h”
#include “..\include\CUtility.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CMesh::CMesh() Toggle

{

m_pMesh = NULL;
m_numMaterials = 0;
m_pMeshMaterials = NULL;
m_ppMeshTextures = NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Cleans up resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CMesh::Release() Toggle

{

// Delete the materials
SAFE_DELETE_ARRAY( m_pMeshMaterials );

// Delete the textures
if ( m_ppMeshTextures )
{

for ( DWORD i = 0; i < m_numMaterials; i++ )
{

SAFE_RELEASE( m_ppMeshTextures[i] );

}

}
SAFE_DELETE_ARRAY( m_ppMeshTextures );
SAFE_RELEASE( m_pMesh );

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Load an X file mesh with no animation.
Parameters:
[in] pDevice – D3D Device
[in] file – File name
Returns: TRUE if load was successful, FALSE otherwise
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CMesh::Load( LPDIRECT3DDEVICE9 pDevice, char* file ) Toggle

{

Release();
LPD3DXBUFFER pMaterialBuffer;
char path[MAX_PATH];
CUtility::GetMediaFile( file, path );
HRESULT hr = D3DXLoadMeshFromX( path, D3DXMESH_MANAGED, pDevice, NULL, &pMaterialBuffer, NULL, &m_numMaterials, &m_pMesh );
if ( FAILED( hr ) )
{

SHOWERROR( “D3DXLoadMeshFromX – Failed”, __FILE__, __LINE__ );
return FALSE;

}

// Store material and texture information
D3DXMATERIAL* pMaterials = (D3DXMATERIAL*)pMaterialBuffer->GetBufferPointer();

m_pMeshMaterials = new D3DMATERIAL9[m_numMaterials];
m_ppMeshTextures = new LPDIRECT3DTEXTURE9[m_numMaterials];

// Copy the materials and textures from the buffer to the member arrays
for ( DWORD i = 0; i < m_numMaterials; i++ )
{

m_pMeshMaterials[i] = pMaterials[i].MatD3D;
m_pMeshMaterials[i].Ambient = m_pMeshMaterials[i].Diffuse;
// Create the texture if it exists
m_ppMeshTextures[i] = NULL;
if( pMaterials[i].pTextureFilename )
{

CUtility::GetMediaFile( pMaterials[i].pTextureFilename, path );
if ( FAILED( D3DXCreateTextureFromFile( pDevice, path, &m_ppMeshTextures[i] ) ) )
{

SHOWERROR( “Failed to load mesh texture”, __FILE__, __LINE__ );
return FALSE;

}

}

}

// Don’t need this no more!
pMaterialBuffer->Release();

return TRUE;

}

The CMesh class is used to load in vertex data from a .x file. To load in the data from the .x file, we first call D3DXLoadMeshFromX. This will load all the vertex data into our ID3DXMesh instance. However, we don’t only want the vertex data, we also want the model’s material and texture data as well. This data is returned through the ID3DXBuffer parameter in the D3DXLoadMeshFromX function.

The ID3DXBuffer Interface is a generic data buffer that we will fill up with material and texture data. To get access to the geometry data in the buffer, we get a pointer to the data with ID3DXBuffer::GetBufferPointer. Since the buffer is a general data buffer, we cast it to a D3DXMATERIAL pointer. A D3DXMATERIAL structure holds two pieces of information. The first is a D3DMATERIAL9 structure. This is the structure that we use to set the current rendering material. The second piece of information is the texture name. we use this string to load in each texture. So after we get the D3DXMATERIAL pointer, we create two arrays to hold the material and texture information. The size of these arrays is returned with the call to D3DXLoadMeshFromX. With the arrays created, we simply loop through all the materials and store each D3DMATERIAL9 into out own array. We also need to set each materials’ ambient color since this isn’t handled by DirectX. To load in the textures, we just call D3DXCreateTextureFromFile. And finally, since we’ve stored our mesh data, we don’t need the ID3DXBuffer any more so we can release it.

class CMeshInstance : public CWorldTransform
{
public:
    CMeshInstance();
    ~CMeshInstance() { Release(); } 

    void Release();
    void SetMesh( CMesh* pMesh );
    void Render( LPDIRECT3DDEVICE9 pevice ); 

private:
    CMesh* m_pMesh;
};

The CMeshInstance class renders the mesh and contains a reference to a CMesh instance. It also inherits from the CWorldTransform class we wrote earlier so we will be able to transform each instance of CMeshInstance.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CMeshInstance::CMeshInstance() Toggle

{

m_pMesh = NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Release
Summary:
Release resouces
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CMeshInstance::Release() Toggle

{

// Mesh data is Released in CMesh
m_pMesh = NULL;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Set the CMesh reference
Parameters:
[in] pMesh – Pointer to a CMesh
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CMeshInstance::SetMesh( CMesh* pMesh ) Toggle

{

Release();
m_pMesh = pMesh;

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the mesh
Parameters:
[in] pDevice – D3D Device
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CMeshInstance::Render( LPDIRECT3DDEVICE9 pDevice ) Toggle

{

if ( pDevice && m_pMesh )
{

pDevice->SetTransform( D3DTS_WORLD, GetTransform() );
DWORD numMaterials = m_pMesh->GetNumMaterials();
for ( DWORD i = 0; i < numMaterials; i++ )
{

pDevice->SetMaterial( m_pMesh->GetMeshMaterial( i ) );
pDevice->SetTexture( 0, m_pMesh->GetMeshTexture( i ) );
m_pMesh->GetMesh()->DrawSubset( i );

}

}

}

The CMeshInstance class is in charge of rendering and transforming a mesh. To initialize CMeshInstance, call SetMesh with a pointer to a CMesh instance. When the CMeshInstance::Render method is called, the method sets the material and texture to those stored in the CMesh instance before rendering a mesh subset. A mesh subset is a group of polygons that share a common material and texture. Meshes are grouped this way so rendering them is more efficient. To render all the subsets of a mesh we need to loop through each subset, set the corresponding material and texture, and call ID3DXBaseMesh::DrawSubset.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary:
This callback function will be called immediately after the Direct3D device has been created. This is
the best location to create D3DPOOL_MANAGED resources. Resources created here should be released in
the OnDestroyDevice callback.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnCreateDevice( LPDIRECT3DDEVICE9 pDevice )
{

// Load meshes
m_mesh.Load( pDevice, “temple.x” );
if ( m_pTemple )
{

for ( int i = 0; i < 2; i++ )
{

m_pTemple[i].Release();

}

}
m_pTemple = new CMeshInstance[2];
m_pTemple[0].SetMesh( &m_mesh );
m_pTemple[1].SetMesh( &m_mesh );

}

By using the mesh wrapper classes, the process of loading in .x files in your projects is as easy as the above code.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Updates the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{

m_pTemple[0].SetXPosition( –5.0f );
m_pTemple[0].ScaleAbs( 0.7f, 0.7f, 0.7f );
m_pTemple[0].RotateRel( 0.0f, D3DXToRadian( 10.0f ) * elapsedTime, 0.0f );

m_pTemple[1].SetXPosition( 5.0f );
m_pTemple[1].ScaleAbs( 0.7f, 0.7f, 0.7f );
m_pTemple[1].RotateRel( 0.0f, D3DXToRadian( 10.0f ) * elapsedTime, 0.0f );

}

Since CMeshInstance inherits from the CWorldTransform class, we can translate, rotate, and scale the mesh however we desire.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Renders the current frame.
Parameters:
[in] pDevice – Pointer to a DIRECT3DDEVICE9 instance
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::OnRenderFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime )
{

pDevice->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
pDevice->BeginScene();

for ( int i = 0; i < 2; i++ )
{

m_pTemple[i].Render( pDevice );

}

pDevice->EndScene();
pDevice->Present( 0, 0, 0, 0 );

}

As you can see, rendering with the MeshInstance class just requires a call to MeshInstance.Render.

Laters, C