Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Introduction to 3D Game Programming with DirectX.9.0 - F. D. Luna

.pdf
Скачиваний:
239
Добавлен:
24.05.2014
Размер:
6.94 Mб
Скачать

174 Chapter 10

// Define the triangles of the box WORD* i = 0; Mesh->LockIndexBuffer(0, (void**)&i);

// fill in the front face index data i[0] = 0; i[1] = 1; i[2] = 2;

i[3] = 0; i[4] = 2; i[5] = 3;

.

.

.

// fill in the right face index data i[30] = 20; i[31] = 21; i[32] = 22; i[33] = 20; i[34] = 22; i[35] = 23;

Y L Once the geometry of the meshFhas been written, we must not forget

Mesh->UnlockIndexBuffer();

in the index buffer exist in Msubset 0, the next four triangles exist in subset 1, and the last four triangles (12 total) exist in subset 2. We express

to specify the subset in which each triangle exists. Recall that the attribute buffer stores the subset to which each triangle in the mesh belongs. In this sample, we specify that the first four triangles defined

this in code as follows:

 

A

DWORD* attributeBuffer = 0;

 

E

 

Mesh->LockAttributeBuffer(0, &attributeBuffer);

T

// triangles 1-4

for(int a = 0; a < 4; a++)

attributeBuffer[a] = 0; // subset 0

for(int b = 4; b < 8; b++)

// triangles 5-8

attributeBuffer[b] = 1; // subset 1

for(int c = 8; c < 12; c++)

// triangles 9-12

attributeBuffer[c] = 2; // subset 2

Mesh->UnlockAttributeBuffer();

Now we have a created mesh that contains valid data. We could render the mesh at this point, but let’s optimize it first. Note that for a trivial box mesh, nothing is really gained by optimizing the mesh data, but nonetheless we get practice using the ID3DXMesh interface methods. In order to optimize a mesh, we first need to compute the adjacency info of the mesh:

std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3);

Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]);

Then we can optimize the mesh, as shown here:

hr = Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT |

D3DXMESHOPT_COMPACT |

D3DXMESHOPT_VERTEXCACHE,

Team-Fly®

Meshes Part I 175

&adjacencyBuffer[0],

0, 0, 0);

At this point, setting up the mesh is complete and we are ready to render it. But there is one last block of code in the Setup function that is relevant. It uses the previously mentioned dump* functions to output the internal data contents of the mesh to file. Being able to examine the data of a mesh helps for debugging and learning the structure of the mesh.

OutFile.open("Mesh Dump.txt");

dumpVertices(OutFile, Mesh); dumpIndices(OutFile, Mesh); dumpAttributeTable(OutFile, Mesh); dumpAttributeBuffer(OutFile, Mesh); dumpAdjacencyBuffer(OutFile, Mesh);

OutFile.close();

...Texturing loading, setting render states, etc., snipped

return true;

} // end Setup()

For example, the dumpAttributeTable function writes the attribute table’s data to file. It is implemented as follows:

void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh)

{

 

 

outFile << "Attribute Table:" << std::endl;

 

outFile << "----------------

" << std::endl << std::endl;

// number of entries in the attribute table

 

DWORD numEntries = 0;

 

 

mesh->GetAttributeTable(0, &numEntries);

 

std::vector<D3DXATTRIBUTERANGE> table(numEntries);

 

mesh->GetAttributeTable(&table[0], &numEntries);

 

for(int i = 0; i < numEntries; i++)

 

{

 

 

outFile << "Entry " << i << std::endl;

 

outFile << "------" << std::endl;

 

outFile << "Subset ID:

" << table[i].AttribId

<< std::endl;

outFile << "Face Start:

" << table[i].FaceStart

<< std::endl;

outFile << "Face Count:

" << table[i].FaceCount

<< std::endl;

outFile << "Vertex Start: " << table[i].VertexStart << std::endl; outFile << "Vertex Count: " << table[i].VertexCount << std::endl; outFile << std::endl;

}

P a r t I I I

176 Chapter 10

outFile << std::endl << std::endl;

}

The following text comes from the Mesh Dump.txt file for this sample application and corresponds to the data written by dumpAttributeTable.

Attribute Table:

----------------

Entry 0

 

------------

 

Subset ID:

0

Face Start:

0

Face Count:

4

Vertex Start:

0

Vertex Count:

8

Entry 1

 

------------

 

Subset ID:

1

Face Start:

4

Face Count:

4

Vertex Start:

8

Vertex Count:

8

Entry 2

 

------------

 

Subset ID:

2

Face Start:

8

Face Count:

4

Vertex Start:

16

Vertex Count:

8

We can see that this matches the data that we specified for the mesh— three subsets with four triangles per subset. We advise you to examine the entire output Mesh Dump.txt file for this sample. It can be found in this sample’s folder in the companion files.

Finally, we can easily render the mesh using the following code; essentially we just loop through each subset, set the associated texture, and then draw the subset. This is easy since we specified the subsets in the order 0, 1, 2, …, n 1, where n is the number of subsets.

bool Display(float timeDelta)

{

if( Device )

{

//...update frame code snipped

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,

0x00000000, 1.0f, 0);

Device->BeginScene();

for(int i = 0; i < NumSubsets; i++)

Meshes Part I 177

{

Device->SetTexture( 0, Textures[i] ); Mesh->DrawSubset( i );

}

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

}

return true;

}

10.10 Summary

A mesh contains a vertex, index, and attribute buffer. The vertex

 

and index buffer hold the geometry of the mesh (vertices and trian-

 

gles). The attribute buffer contains a corresponding entry for each

 

triangle and specifies the subset to which a triangle belongs.

 

A mesh can be optimized with the OptimizeInplace or Opti-

 

mize method. Optimization reorganizes the geometry of the mesh

 

to make rendering more efficient. Optimizing a mesh with

 

D3DXMESHOPT_ATTRSORT generates an attribute table. An attrib-

 

ute table allows the mesh to render an entire subset using a simple

 

lookup into the attribute table.

 

The adjacency info of a mesh is a DWORD array that contains three

 

entries for every triangle in the mesh. The three entries corre-

 

 

sponding to a particular triangle specify the triangles that are adja-

I

cent to that triangle.

II

art

We can create an empty mesh using the D3DXCreateMeshFVF

function. We can then write valid data to the mesh using the appro-

P

 

priate locking methods (LockVertexBuffer, LockIndexBuf-

 

fer, and LockAttributeBuffer).

 

Chapter 11

Meshes Part II

In this chapter we continue our study of the mesh-related interfaces, structures, and functions provided by the D3DX library. With the foundation built in the last chapter, we can move on to more interesting techniques, such as loading and rendering complex 3D models stored on disk and controlling the level of detail of our meshes through the progressive mesh interface.

Objectives

To learn how to load the data of an XFile into an ID3DXMesh object

To gain an understanding of the benefits of using progressive meshes and how to use the progressive mesh interface—

ID3DXPMesh

To learn about bounding volumes, why they are useful, and how to create them using the D3DX functions

11.1ID3DXBuffer

A small reference to the ID3DXBuffer interface was made in the last chapter, but we didn’t elaborate on it. We see this interface throughout our utilization of the D3DX library, and therefore a brief overview of this interface is called for.

The ID3DXBuffer interface is a generic data structure that D3DX uses to store data in a contiguous block of memory. It has only two methods:

LPVOID GetBufferPointer();—Returns a pointer to the start of the data

DWORD GetBufferSize();—Returns the size of the buffer in bytes

To keep the structure generic, it uses a void pointer. This means that it is up to us to realize the type of data being stored. For example,

D3DXLoadMeshFromX uses an ID3DXBuffer to return the adjacency

178

Meshes Part II 179

info of a mesh. Since adjacency info is stored as a DWORD array, we have to cast the buffer to a DWORD array when we wish to use the adjacency info from the buffer.

Examples:

DWORD* info =(DWORD*)adjacencyInfo->GetBufferPointer();

D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();

Since an ID3DXBuffer is a COM object, it must be released when you are done with it to avoid a memory leak:

adjacencyInfo->Release();

mtrlBuffer->Release();

We can create an empty ID3DXBuffer using the following function:

HRESULT D3DXCreateBuffer(

 

DWORD NumBytes,

// Size of the buffer, in bytes.

LPD3DXBUFFER *ppBuffer // Returns the created buffer.

);

The following example creates a buffer that can hold four integers:

ID3DXBuffer* buffer = 0;

D3DXCreateBuffer( 4 * sizeof(int), &buffer );

11.2 XFiles

Thus far, we have worked with simple geometric objects, such as spheres, cylinders, cubes, etc., using the D3DXCreate* functions. If you have attempted to construct your own 3D object by manually specifying the vertices, you have, no doubt, found it quite tedious. To alleviate this tiresome task of constructing the data of 3D objects, special applications called 3D modelers have been developed. These modelers allow the user to build complex and realistic meshes in a visual and interactive environment with a rich tool set, making the entire modeling process much easier. Examples of popular modelers used for game development are 3DS Max (www.discreet.com), LightWave 3D (www.newtek.com), and Maya (www.aliaswavefront.com).

These modelers, of course, can export the created mesh data (geometry, materials, animations, and other possible useful data) to a file. Thus, we could write a file reader to extract the mesh data and use it in our 3D applications. This is certainly a viable solution. However, an even more convenient solution exists. There is a particular mesh file format called the XFile format (with the extension .X). Many 3D modelers can export to this format and there exist converters that can

P a r t I I I

180 Chapter 11

convert other popular mesh file formats to .X. Now, what makes XFiles convenient is that they are a DirectX defined format, and therefore the D3DX library readily supports XFiles. That is, the D3DX library provides functions for loading and saving XFiles. Thus, we avoid having to write our own file loading/saving routines if we use this format.

Note: You can download the DirectX9 SDK Extra—Direct3D Tools package from MSDN at http://www.msdn.microsoft.com/ to get some already made .X exporters for popular 3D modelers like 3DS Max, LightWave, and Maya.

11.2.1 Loading an XFile

We use the following function to load the mesh data stored in an XFile. Note that this method creates an ID3DXMesh object and loads the geometric data of the XFile into it.

HRESULT D3DXLoadMeshFromX(

LPCSTR pFilename,

DWORD Options,

LPDIRECT3DDEVICE9 pDevice,

LPD3DXBUFFER *ppAdjacency,

LPD3DXBUFFER *ppMaterials, LPD3DXBUFFER* ppEffectInstances, PDWORD pNumMaterials, LPD3DXMESH *ppMesh

);

pFilename—The filename of the XFile to load

Options—One or more creation flags that are used to create the mesh. See the D3DXMESH enumerated type in the SDK documentation for a complete list of option flags. Some common flags are:

D3DXMESH_32BIT—The mesh will use 32-bit indices.

D3DXMESH_MANAGED—The mesh will be placed in the managed memory pool.

D3DXMESH_WRITEONLY—The mesh’s data will only be written to and not read from.

D3DXMESH_DYNAMIC—The mesh’s buffers will be made dynamic.

pDevice—The device to be associated with the mesh

ppAdjacency—Returns an ID3DXBuffer containing a DWORD array that describes the adjacency info of the mesh

ppMaterials—Returns an ID3DXBuffer containing an array of D3DXMATERIAL structures that contains the material data for this mesh. We cover the mesh materials in the following section.

Meshes Part II 181

ppEffectInstances—Returns an ID3DXBuffer containing an array of D3DXEFFECTINSTANCE structures. We ignore this parameter for now by specifying 0.

pNumMaterials—Returns the number of materials for the mesh (that is, the number of elements in the D3DXMATERIAL array output by ppMaterials).

ppMesh—Returns the created ID3DXMesh object filled with the XFile geometry

11.2.2 XFile Materials

Argument seven of D3DXLoadMeshFromX returns the number of materials that the mesh contains, and argument five returns an array of D3DXMATERIAL structures containing the material data. The D3DXMATERIAL structure is defined as follows:

typedef struct D3DXMATERIAL { D3DMATERIAL9 MatD3D;

LPSTR pTextureFilename;

} D3DXMATERIAL;

It is a simple structure; it contains the basic D3DMATERIAL9 structure and a pointer to a null-terminating string that specifies the associative texture filename. An XFile doesn’t embed the texture data; rather it embeds the filename, which is then used as a reference to the image file that contains the actual texture data. Thus, after we load an XFile with D3DXLoadMeshFromX, we must load the texture data given the texture filenames. We show how to do this in the next section.

It is worth noting that the D3DXLoadMeshFromX function loads the XFile data so that the ith entry in the returned D3DXMATERIAL array corresponds with the ith subset. Thus, the subsets are labeled in the order 0, 1, 2, …, n – 1, where n is the number of subsets and materials. This allows the mesh to be rendered as a simple loop that iterates through each subset and renders it.

11.2.3 Sample Application: XFile

We now show the relevant code to the first sample of this chapter called XFile. The sample loads an .x file called bigship1.x that was taken from the media folder of the DirectX SDK. The complete source code can be found in the companion files. Figure 11.1 shows a screen shot of the sample.

P a r t I I I

182 Chapter 11

Figure 11.1: A screen shot taken from the XFile sample

This sample uses the following global variables:

ID3DXMesh*

Mesh = 0;

std::vector<D3DMATERIAL9>

Mtrls(0);

std::vector<IDirect3DTexture9*> Textures(0);

Here we have an ID3DXMesh object that is used to store the mesh data that we load from the XFile. We also have a vector of materials and textures that we use to hold the mesh’s materials and textures.

We begin by implementing our standard Setup function. First, we load the XFile:

bool Setup()

{

HRESULT hr = 0;

//

// Load the XFile data.

//

ID3DXBuffer* adjBuffer = 0;

ID3DXBuffer*

mtrlBuffer =

0;

DWORD

numMtrls =

0;

hr = D3DXLoadMeshFromX( "bigship1.x", D3DXMESH_MANAGED, Device, &adjBuffer, &mtrlBuffer,

0,

&numMtrls,

&Mesh);

if(FAILED(hr))

{

Meshes Part II 183

::MessageBox(0, "D3DXLoadMeshFromX() - FAILED", 0, 0); return false;

}

After we have loaded the XFile data, we must iterate through the D3DXMATERIAL array and load any textures that the mesh references:

//

// Extract the materials, load textures.

//

if( mtrlBuffer != 0 && numMtrls != 0 )

{

D3DXMATERIAL* mtrls=(D3DXMATERIAL*)mtrlBuffer-> GetBufferPointer();

for(int i = 0; i < numMtrls; i++)

{

//the MatD3D property doesn't have an ambient value

//set when it’s loaded, so set it now: mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;

//save the ith material

Mtrls.push_back( mtrls[i].MatD3D );

//check if the ith material has an associative

//texture

if( mtrls[i].pTextureFilename != 0 )

{

//yes, load the texture for the ith subset IDirect3DTexture9* tex = 0; D3DXCreateTextureFromFile(

Device,

mtrls[i].pTextureFilename,

&tex);

//save the loaded texture

Textures.push_back( tex );

}

else

{

// no texture for the ith subset Textures.push_back( 0 );

}

}

}

d3d::Release<ID3DXBuffer*>(mtrlBuffer); // done w/ buffer

.

. // Snipped irrelevant code to this chapter (e.g., setting up lights,

. // view and projection matrices, etc.)

.

return true;

} // end Setup()

P a r t I I I