Creating Subsets in ID3DXMesh

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Written by Robert Dunlop
Microsoft DirectX MVP

Related Articles of Interest:

Using the ID3DXMesh Class

The ID3DXMesh class provided in DX 8 allows you to divide a mesh into several parts, or subsets, that can be rendered independently of each other using the ID3DXMesh::DrawSubset() method.  This allows you to apply different materials, textures, or renderstates to different portions of a mesh, or to selectively render only certain portions of a mesh.

To do this, you must set the attribute ID for each face, to define which subset the face belongs to.  First, call ID3DXMesh::LockAttributeBuffer() to get a pointer to the attribute buffer, which is an array of DWORDs that defines the subset number for each face.  The subset numbers can be any 32 bit value you choose, though it is typical to use values 0 to n-1 to define n subsets.

Once you have locked the attribute buffers, you can set the desired attribute values using the buffer pointer, then unlock the attribute buffer using ID3DXMesh::UnlockAttributeBuffer() when the changes are complete.

At this point, you can use ID3DXMesh::DrawSubset() to render a subset, but you will first want to perform one more step for the sake of performance.  If you were to call ID3DXMesh::DrawSubset() at this point, it would have to scan through the attribute buffer to determine which faces belong to the requested subset.  Instead, if you call ID3DXMesh::OptimizeInPlace() or ID3DXMesh::Optimize() with the D3DXMESHOPT_ATTRSORT flag (see note below), the index and vertex buffers will be sorted such that each subset represents a contiguous sequence of faces.  This call will also generate an "attribute table", which is an array of one D3DXATTRIBUTERANGE structure per subset defining the starting and ending face numbers for each range, and the range of vertices used.  Once this is performed, ID3DXMesh::DrawSubset() merely has to look up the range of faces and vertices to draw from the attribute table, providing far greater performance.

Note: While D3DXMESHOPT_ATTRSORT is sufficient to set up the attribute tables, it does not provide further optimization to improve vertex cache performance.  It is highly recommended that you instead use the D3DXMESHOPT_VERTEXCACHE flag, which will create the attribute table and also reorder the vertices to make optimum use of the vertex cache.

Here is an example of a function that divides a mesh into a series of irregularly sized subsets, allowing subsets to consist of non-contiguous series of faces:

// define subset info we will assign
DWORD faceCount[] = {  50, 100,  50, 100, 200,   0};
DWORD subsetNum[] = {   0,   1,   0,   2,   3 };
// First 50 faces (0-49) belong to subset 0
// Next 100 faces (50-149) belong to subset 1
// Next 50 faces (150-199) also belong to subset 0
// Next 100 faces (200-299) belong to subset 2
// Next 200 faces (300-499) belong to subset 3
// 0 faces in final faceCount[] index denotes end of list

if (FAILED(SetSubsets(pMesh,faceCount,subsetNum)) {
    // handle error
    ...
}

DWORD SetSubsets(ID3DXMesh *pMesh,DWORD *pFaceCount,DWORD *pSubsetNum) {
   
    // Get face count, used to ensure we don't overrun attribute buffer
    DWORD numFaces=pMesh->GetFaceCount();
 
    // lock the attribute buffer
    DWORD *attribBuf;
    HRESULT hr;
    if (SUCCEEDED(hr=pMesh->LockAttributeBuffer(D3DLOCK_DISCARD,&attribBuf))) {

        // initialize face counter
        DWORD faceNum=0;

        // loop through the subsets
        for (int i=0;pFaceCount[i];i++) {

            // make sure there are enough faces for this subset
            if (faceNum+pFaceCount[i]>=numFaces) {
                // not enough faces, unlock the attribute buffer and return error code
                pMesh->UnlockAttributeBuffer();
                return E_INVALIDARG;
            }
 
            // loop through the faces for this range
            for (int j=0;j<pFaceCount[i];i++) {

                // set the subset number of each face in this range
                attribBuf[faceNum]=pSubsetNum[i];
           
                // increment face counter
                faceNum++;
            }
        }

        // unlock the attribute buffer
        pMesh->UnlockAttributeBuffer();

        // allocate storage and generate adjacency data
        DWORD *pAdj=new DWORD[numFaces*3];
        if (!pAdj)
            return E_OUTOFMEMORY;
        if (FAILED(hr=pMesh->GenerateAdjacency(0.0f,pAdj) {
            delete pAdj;
            return hr;
        }
        // optimize the mesh with attribute sorting
        // D3DXMESHOPT_ATTRSORT
        if (FAILED(hr=pMesh->OptimizeInPlace(D3DXMESHOPT_VERTEXCACHE,pAdj,NULL,NULL,NULL)) {
            delete pAdj;
            return hr;
        }

        // de-allocate adjacency data storage
        delete pAdj;

    } else

        // return error code on failure to lock attribute buffer
        return hr;

    // return success
    return S_OK;
}

Note that if you already have adjacency data for the mesh, you won't have to re-compute it in the function as I have here - I included that for simplicity.  If you already have adjacency data you can pass it to the optimization function, and may also need to provide a pointer to an array to contain the new adjacency information, as a parameter to the optimization function, if you have further use for this data.

For more information on using the D3DX Mesh interfaces, see Using the ID3DXMesh Class.

Related Articles of Interest:

Using the ID3DXMesh Class

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.