10. Mesh Part One
本文译自《Introduction to 3D Game Programming with DirectX 9.0》第十章“Mesh Part One”,敬请斧正。
在D3DX中,有很多函数都使用了ID3DXMesh接口,如D3DXCreate*之类。ID3DXMesh接口的主要功能继承自ID3DXBaseMesh父类接口,还有其他的Mesh接口也是从ID3DXBaseMesh接口继承的,如ID3DXPMesh接口,这个接口用于Progressive Mesh,把它翻译成“渐进Mesh”,不知是否合适。
本节要达到的目标:
l 学习ID3DXMesh对象的内部数据组织
l 学习创建一个ID3DXMesh对象
l 学习优化ID3DXMesh
l 学习渲染ID3DXMesh
10.1. 几何结构信息
接口ID3DXBaseMesh具有顶点缓冲和顶点索引缓冲,分别用于存储Mesh的顶点数据和顶点的索引数据,二者结合在一起才能够渲染出组成Mesh的三角形。使用下面的两个方法可得到指向两个缓冲区的指针:
HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9* ppVB); HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9* ppIB); |
下面是一个关于上述两个方法用法的例子:
IDirect3DVertexBuffer9* pVB=NULL; Mesh->GetVertexBuffer(&pVB);
IDirect3DIndexBuffer9* pIB=NULL; Mesh->GetIndexBuffer(&pIB); |
另外,如果想修改顶点缓冲和顶点索引缓冲,需要先使用下面的两个方法加锁:
HRESULT ID3DXMesh::LockVertexBuffer(DWORD Flags,LPVOID* ppData); HRESULT ID3DXMesh::LockIndexBuffer(DWORD Flags,LPVOID* ppData); |
参数Flags用于说明加锁的方式,参数ppData返回被锁定的内存的地址。记住,如果加锁成功还需要调用与之配对的解锁函数:
HRESULT ID3DXMesh::UnlockVertexBuffer(); HRESULT ID3DXMesh::UnlockIndexBuffer(); |
下面是另外一些与Mesh的几何结构有关的ID3DXMesh接口的方法:
l DWORD GetFVF(); --返回顶点的格式
l DWORD GetNumVertices(); --返回顶点缓冲中的顶点数
l DWORD GetNumBytesPerVertex(); --返回一个顶点所占的字节数
l DWORD GetNumFaces(); --返回Mesh的面数,也就是三角形数
10.2. 子集和属性缓冲
一个Mesh由数个子集组成。子集是Mesh中的一组使用相同属性渲染的三角形。这里的属性指的是材质、纹理、渲染状态。每一个子集用一个唯一的非负整数表示其ID,如0,1,2,3等。
Mesh中的每一个三角形都与一个属性ID相关联,表示该三角形属于该子集。例如,在一个表示房子的Mesh中,组成地板的三角形具有属性ID 0,这就表示这些三角形属于子集0;同样的,组成墙的三角形的属性ID为1,他们属于子集1。
三角形的属性ID存储在Mesh的属性缓冲中,这是一个DWORD数组。因为每个面对应属性缓冲中的一项,所以属性缓冲中的项目数等于Mesh中的面的个数。属性缓冲中的项目和索引缓冲定义的三角形一一对应;也就是说,属性缓冲的第I项和索引缓冲中定义的第I个三角形相对应。三角形I有下面三个索引缓冲中的索引项定义:
A=I*3
B=I*3 + 1
C=I*3 + 2
可以使用下面的方法访问属性缓冲:
DWORD* buffer=NULL; Mesh->LockAttributeBuffer(lockingFlags,&buffer); // do something... Mesh->UnlockAttributeBuffer(); |
10.3. 渲染
接口ID3DXMesh提供了DrawSubset(DWORD AttribID)方法渲染参数AttribID指示的子集中的各个三角形。例如,如果渲染子集0中的所有三角形,可以使用如下方法:
Mesh->DrawSubset(0); |
如果要渲染整个Mesh,需要分别渲染Mesh的各个子集。因为子集序列与Mesh使用的材质、纹理的序列相对应,即子集I和材质、纹理数组的第I项对应,所以可以使用一个简单的循环渲染Mesh:
for (int i=0;i<numSubsets;i++) { Device->SetMaterial(mtrls[i]); Device->SetTexture(0,textures[i]); Mesh->DrawSubset(i); } |
10.4. 优化
为了更加有效的渲染Mesh,可以重新组织其中的顶点和索引,也就是优化Mesh。可以使用如下方法进行优化:
HRESULT ID3DXMesh::OptimizeInplace( DWORD Flags, CONST DWORD *pAdjacencyIn, DWORD *pAdjacencyOut, DWORD *pFaceRemap, LPD3DXBUFFER *ppVertexRemap ); |
l Flags –优化选项,告诉该方法执行什么类型的优化。可以区下面的一个或几个值:
n D3DXMESHOPT_COMPACT –删除没有用的顶点和索引项
n D3DXMESHOPT_ATTRSORT –根据属性给三角形排序并调整属性表,这将使DrawSubset方法更有效的执行
n D3DXMESHOPT_VERTEXCACHE –增加顶点缓冲的命中率
n D3DXMESHOPT_STRIPREORDER –重组顶点索引使三角形条带(Triangle Strip)尽量长
n D3DXMESHOPT_IGNOREVERTS –只优化索引,忽略顶点
l pAdijacencyIn –没有优化的Mesh的邻接数组
l pAdjacencyOut –输出优化的Mesh的邻接信息的数组。这个DWORD数组必须有ID3DXMesh::GetNumFaces() * 3个元素。如果不需要该信息,可以传递NULL。
l pFaceRemap –一个DWORD数组,用于接收面重影射信息。这个数组应不小于ID3DXMesh::GetNumFaces()。当Mesh被优化时,由索引缓冲定义的面可能被移动,也就是说,如果pFaceRemap的第I项表示第I个原始面被移到的面索引值。如果不需要该信息,可以使用NULL。
l ppVertexRemap –指向ID3DXBuffer的指针的地址,返回顶点重影射信息。该缓冲区应包含ID3DXMesh::GetNumVertices()个顶点。当Mesh被优化时,顶点可能被移动,该重影射信息用于说明原来的顶点被移动到新位置,也就是说,ppVertexRemap的第I项指示原来的第I个顶点的新位置。如果不需要该信息,可以使用NULL。
// Get the adjacency info of the non-optimized mesh. DWORD adjacencyInfo[Mesh->GetNumFaces() * 3]; Mesh->GenerateAdjacency(0.0f, adjacencyInfo); // Array to hold optimized adjacency info. DWORD optimizedAdjacencyInfo[Mesh->GetNumFaces() * 3]; Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, adjacencyInfo, optimizedAdjacencyInfo, 0, 0); |
另一个相似的方法是Optimize(),它输出一个优化的Mesh,而不是在原来Mesh的基础上进行优化:
HRESULT ID3DXMesh::Optimize( DWORD Flags, CONST DWORD *pAdjacencyIn, DWORD *pAdjacencyOut, DWORD *pFaceRemap, LPD3DXBUFFER *ppVertexRemap, LPD3DXMESH *ppOptMesh ); |
10.5. 属性表
如果一个Mesh使用D3DXMESHOPT_ATTRSORT标志进行优化,Mesh的结构信息将按属性进行排序,这样各个子集的顶点/顶点索引将组成连续的块。
除了进行几何信息的排序外,D3DXMESHOPT_ATTRSORT优化选项还将创建一个属性表。该表是D3DXATTRIBUTERANGE结构的一个数组,其中的每一项对应Mesh的一个子集并指示顶点/顶点索引的一个连续块,这个子集的几何信息就包含在这个块里。结构D3DXATTRIBUTERANGE的定义如下:
typedef struct _D3DXATTRIBUTERANGE { DWORD AttribId; DWORD FaceStart; DWORD FaceCount; DWORD VertexStart;
|