DirectX 8 Graphics introduced a series of helper interfaces and functions for handling various aspects of mesh creation, manipulation, and rendering.
The interfaces are derived from a base class, ID3DXBaseMesh, which is the basis for several different interfaces used to encapsulate mesh objects:
In this article, we will focus on the ID3DXMesh interface. This function provides basic mesh support, through the encapsulation of the following components:
There are a number of functions that can be used to create an ID3DXMesh mesh object. We'll group them into four categories:
There are two basic functions used for creation of an empty mesh container, D3DXCreateMesh() and D3DXCreateMeshFVF(). These functions vary in the definition of the vertex format - the first accepts a declarator, while the later accepts a combination of flexible vertex format flags. The syntax of these functions is:
These functions take the following parameters:
The following shape creation functions create mesh objects containing various shapes:
See the SDK documentation for the syntax of these functions.
Functions are provided that allow you to load objects from a mesh file into one or more ID3DXMesh objects, as well as attaining the material and texture information needed to render the objects.
There are two functions that can be used to create ID3DXMesh objects from X files. The first, D3DXLoadMeshFromX(), accepts a file name and loads the requested X file into a new mesh. If there are multiple objects in the file, they are collapsed into a single mesh. The second loading function is D3DXLoadMeshFromXof(), which can be used to load an object from an open X file through a pointer to an IDirectXFileData interface passed to this function.
In addition to returning a new mesh object, these functions may return a buffer containing an array of one or more D3DXMATERIAL structures, which contain the materials and the file names of the textures to be used when rendering the mesh. If more than one material is returned, as indicated by the material count returned by the mesh loading function, then there are multiple subsets in the mesh. The loader will already have tagged each face with the appropriate attribute ID (subset number), ranging from 0 to the one less than the number of materials. For more information on this, see Using Attributes to Define Subsets and Rendering with DrawSubset() later in this article
Several methods are provided by the base class that allow you to retrieve information about the mesh:
An ID3DXMesh object contains a vertex buffer and an index buffer. You can access these using methods provided by the interface, either by getting a pointer to the buffer object that you can then lock and access, or by having the mesh object lock the buffer itself and return a pointer into the buffer for direct access.
The vertex buffer can be accessed through one of two methods: ID3DXMesh::GetVertexBuffer(), or ID3DXMesh::LockVertexBuffer(). The GetVertexBuffer() method returns a pointer to an IDirect3DVertexBuffer8 object, which you can then lock for access. Be sure to release the vertex buffer after you are done with it, as the reference counter will be incremented by this call:
A more convenient alternative is offered by the LockVertexBuffer() method, which handles the lock/unlock functionality, and returns a pointer directly to the vertex data:
Like the vertex buffer, the index buffer can be accessed through one of two methods: ID3DXMesh::GetIndexBuffer(), or ID3DXMesh::LockIndexBuffer(). The GetIndexBuffer() method returns a pointer to an IDirect3DIndexBuffer8 object, which you can then lock for access. Be sure to release the index buffer after you are done with it, as the reference counter will be incremented by this call:
A more convenient alternative is offered by the LockIndexBuffer() method, which handles the lock/unlock functionality, and returns a pointer directly to the index data:
The ID3DXMesh interface allows the mesh to be divided into separate subsets, which can be rendered separately. This is achieved through the use of attribute IDs, which are stored as a DWORD value for each face in the mesh. These values specify an ordinal defining which group each face belongs to. While it is common convention to use sequential numbers starting at zero, you may use any arbitrary values within the range of a DWORD to define your groups.
With the exception of the X file loading functions, upon initial creation of a mesh through one of the D3DX mesh functions all of the faces in the mesh will have an attribute ID of zero. This facilitates rendering the entire mesh without having to set attribute IDs, if division of the mesh is not required.
The X file loading functions (D3DXLoadMeshFromX() and D3DXLoadMeshFromXof()) automatically set the attribute IDs, generating a group for each material / texture combination present. These will be numbered sequentially, starting at group zero.
If you need to manually define subsets, you can use the ID3DXMesh::LockAttributeBuffer() and ID3DXMesh::UnlockAttributeBuffer() methods to access an array of DWORDs corresponding to the attribute IDs for each face. For example, if we had a mesh with 150 faces, and we wanted to divide it into two groups comprised on the first 100 faces as group 0 and the remaining faces as group 1, we could do the following:
Note that while the example uses consecutive groups of faces, this does not have to be the case - you can assign IDs in any manner you wish. However, this raises a performance issue, because the vertices and indices in a group may not be sequential. Left like this, if we were to call on the mesh object to render a group, it would perform a linear search of the attribute IDs, causing a significant performance hit.
Fortunately, there is a ready solution to this. By calling the Optimize() or OptimizeInPlace() methods of the mesh (further discussed in Optimizing Meshes) with the D3DXMESHOPT_ATTRSORT flag, the mesh is optimized for rendering of the groups defined by the attribute IDs. In addition to sorting the vertices and indices, it defines D3DXATTRIBUTERANGE structures specifying the ranges of vertices and indices used by each subset.
For a more generalized function to defining mesh subsets, see Creating Subsets in ID3DXMesh.
The DrawSubset() method is used to render a specified subset of the mesh using the current renderstates. A common usage is to iterate through the subsets and render them with different materials and textures, as specified in an X file. For example, we can load an X file and its textures something like this:
Then, we can iterate through the subsets and draw them with the appropriate materials and textures:
for( UINT i = 0; i < m_dwNumMaterials; i++ )
Meshes can be optimized for improved performance using the ID3DXBaseMesh::Optimize() and ID3DXBaseMesh::OptimizeInPlace() methods. Optimize() creates a new mesh, while OptimizeInPlace() optimizes an existing mesh.
These functions take one or more of the following flags:
Note that the D3DXMESHOPT_STRIPREORDER and D3DXMESHOPT_VERTEXCACHE optimization flags are mutually exclusive.
The ID3DXBaseMesh::Clone() and ID3DXBaseMesh::CloneFVF() functions allow you to make copies of an existing mesh object, that contain a copy of the mesh but may have a different vertex format. This is useful when vertex components must be added to a mesh, as shown in the section Generating Normals, below. Clone() and CloneFVF() take a declarator or a combination of FVF flags, respectively.
Calculating normals for meshes stored in an ID3DMesh object is a snap, using the D3DX helper functions. The D3DXComputeNormals() function takes a pointer to a mesh object, and computes the vertex normals by averaging the face normals for all the faces that share a vertex.
If the mesh vertex format does not currently include normals, then we can use the CloneMeshFVF() method to generate a new mesh interface with a vertex format that includes normals:
Visitors Since 1/1/2000: