|
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,需要分别渲染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;
DWORD VertexCount;
} D3DXATTRIBUTERANGE; |
l AttribId –子集的ID
l FaceStart –该子集的面的起始值,FaceStart*3就是起始三角形在索引缓冲的索引偏移
l FaceCount –子集中的面数,也就是三角形数
l VertexStart –该子集的起始顶点在顶点缓冲中的偏移
l VertexCount –该子集包含的定点数
很容易看出该结构与上面图中表示的信息之间的联系。上图中Mesh的属性表中的每一项对应一个子集。
建立属性表后,渲染一个子集就很容易了,仅仅查一下属性表找出该自己的几何信息。如果没有属性表,每渲染一个子集就需要对属性缓冲区进行一次线性搜索来找出该子集包含的几何信息。
可以使用如下方法访问Mesh的属性表:
|
HRESULT ID3DXMesh::GetAttributeTable(
D3DXATTRIBUTERANGE *pAttribTable,
DWORD *pAttribTableSize
); |
该方法可以完成两个功能:可以返回属性表的属性数,也可以将返回完整的属性表。
要得到属性表的元素个数,可以给第一个参数传NULL,如:
|
DWORD numSubsets = 0;
Mesh->GetAttributeTable(0, &numSubsets); |
然后,就可以取得属性表了:
|
D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE [numSubsets];
Mesh->GetAttributeTable( table, &numSubsets ); |
还可以使用ID3DXMesh::SetAttributeTable方法直接修改属性表。
|
D3DXATTRIBUTERANGE attributeTable[12];
// ...fill attributeTable array with data
Mesh->SetAttributeTable( attributeTable, 12); |
10.6. 邻接信息
对于Mesh的某些操作,如优化,需要知道三角形间的邻接信息,而Mesh的邻接数组就存储这样的信息。
邻接数组是DWORD类型数组,其中的每一项对应Mesh中的一个三角形。例如,邻接数组的第I项对应的三角形有以下三个索引值定义:
A=I*3
B=I*3 + 1
C=I*3 +2
这里,使用ULONG_MAX=4294967295表示该边没有邻接三角形。其实,这个数就是-1。
由于每个三角形有三条边,所以,他有三个邻接三角形。

因此,每个三角形可能有三个邻接三角形,邻接数组必须有(ID3DXMesh::GetNumFaces() * 3)个元素。
在D3DX中,有很多函数可以输出Mesh的邻接信息,如:
|
HRESULT ID3DXMesh::GenerateAdjacency(
FLOAT fEpsilon,
DWORD* pAdjacency
); |
l fEpsilon –指示当两点距离有多近时,可以认为是一个点。当两点间的距离小于epsilon时,可认为他们是同一个点
l pAdjacency –用于存储邻接信息的邻接数组
例如:
|
DWORD adjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->GenerateAdjacency(0.001f, adjacencyInfo); |
10.7. 克隆
有时,需要将Mesh的数据另外复制一份,可以使用ID3DXMesh::CloneMeshFVF方法:
|
HRESULT ID3DXMesh::CloneMeshFVF(
DWORD Options,
DWORD FVF,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXMESH *ppCloneMesh
); |
l Options –创建Mesh的标志。完整信息可参考SDK文档。下面的几个很常用:
n D3DXMESH_32BIT –使用32位顶点索引
n D3DXMESH_MANAGED –Mesh数据将被放在受控的内存缓冲池中
n D3DXMESH_WRITEONLY –Mesh数据只能执行写操作,不能执行读操作
n D3DXMESH_DYNAMIC –Mesh缓冲将是动态的
l FVF –创建新Mesh的灵活定点格式
l pDevice –与克隆Mesh相关联的D3D设备
l ppCloneMesh –输出新Mesh
这个方法允许指定与原Mesh不同的Options和FVF。例如,现在有一个定点格式为D3DXFVF_XYZ的Mesh,我们想复制一个顶点格式为D3DXFVF_XYZ|D3DXFVF_NORMAL的Mesh,可以这样做:
|
// assume _mesh and device are valid
ID3DXMesh* clone = 0;
Mesh->CloneMeshFVF(
Mesh->GetOptions(), // use same options as source mesh
D3DFVF_XYZ | D3DFVF_NORMAL,// specify clones FVF
Device,
&clone); |
10.8. 创建Mesh(D3DXCreateMeshFVF)
我们可以使用D3DXCreateMeshFVF函数创建一个空的Mesh对象。所谓空,是指我们已经指定了顶点数和面数(也就是三角形数),函数D3DXCreateMeshFVF也分配了适当大小的内存给顶点、定点索引、属性缓冲区。有了这些缓冲区后,就可以手动填写上下文数据了(需要分别向定点缓冲区、索引缓冲区、属性缓冲区提供定点、索引、属性数据)。
|
HRESULT WINAPI D3DXCreateMeshFVF(
DWORD NumFaces,
DWORD NumVertices,
DWORD Options,
DWORD FVF,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXMESH *ppMesh
); |
l NumFaces –Mesh中的三角形数,必须指定一个大于0的数
l NumVertices –定点数,也必须是一个大于0的数
l Options –创建Mesh的选项标志,常用的标志如下:
n D3DXMESH_32BIT –Mesh使用32位顶点索引
n D3DXMESH_MANAGED –使用受控内存池
n D3DXMESH_WRITEONLY –Mesh的数据只能被写,不能被读
n D3DXMESH_DYNAMIC –使用动态缓冲
l FVF –Mesh的顶点格式
l pD3DDevice –与Mesh相关联的D3D设备
l ppMesh –输出的Mesh指针
在下一节,将举例说明该函数的用法,到时将手动填充Mesh对象的数据。
另外,还可以使用D3DXCreateMesh函数创建空的Mesh对象。其原形如下:
|
HRESULT WINAPI D3DXCreateMesh(
DWORD NumFaces,
DWORD NumVertices,
DWORD Options,
const LPD3DVERTEXELEMENT9 *pDeclaration,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXMESH *ppMesh
); |
其中的参数的含义就不需要再解释了,他们与D3DXCreateMeshFVF的参数相似。但是,第四个参数是D3DVERTEXELEMENT9的数组,而不是先前的FVF。
< |