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。
10.9. 应用举例:创建并渲染Mesh 我们手动创建一个立方体,它将使用本章讨论的大部分知识点:
l 创建一个空的Mesh
l 向Mesh中填写立方体的几何信息
l 划分子集
l 生成Mesh的邻接信息
l 优化
l 渲染
这里,给出代码的框架,这个例子使用D3DXCreateMeshFVF函数创建Mesh对象。
在代码中,定义如下的全局变量:
ID3DXMesh* Mesh = 0;
const DWORD NumSubsets = 3;
IDirect3DTexture9* Textures[3] = {0, 0, 0};// texture for each subset
第一个是Mesh对象的指针;Mesh对象中有三个子集。每个子集用不同的纹理渲染,数组Textures中存储每个子集的纹理。
主要的代码在Setup函数中,他首先创建一个空的Mesh对象:
bool Setup()
{
HRESULT hr = 0;
hr = D3DXCreateMeshFVF(
12,
24,
D3DXMESH_MANAGED,
Vertex::FVF,
Device,
&Mesh);
这里,创建的Mesh具有12个面(三角形),24个顶点,使用他们来描述立方体。
现在,Mesh对象还是空的,需要为其填充适当的顶点/索引数据。
// Fill in vertices of a box
Vertex* v = 0;
Mesh->LockVertexBuffer(0, (void**)&v);
// fill in the front face vertex data
v[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
.
.
.
v[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
Mesh->UnlockVertexBuffer();
// 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;
Mesh->UnlockIndexBuffer();
立方体的几何信息输入完毕后,还需要为各个三角形划分子集。可以通过修改Mesh对象的属性缓冲区,指定每个三角形所属的子集。在这个例子中,由索引缓冲定义的12个三角形中,前四个属于子集0,再下面四个属于子集1,最后四个属于子集2。
DWORD* attributeBuffer = 0;
Mesh->LockAttributeBuffer(0, &attributeBuffer);
for(int a = 0; a < 4; a++) // triangles 1-4
attributeBuffer[a] = 0; // subset 0
for(int b = 4; b < 8; b++) // triangles 5-8
attributeBuffer = 1; // subset 1
for(int c = 8; c < 12; c++) // triangles 9-12
attributeBuffer[c] = 2; // subset 2
Mesh->UnlockAttributeBuffer();
到这里,这个Mesh对象已经创建完毕了,可以渲染了。但是,它还没有经过优化。当然,对于这个微不足道的立方体,优化得不到任何好处,但是,我们还是用它演示ID3DXMesh接口方法的用法。在优化之前,需要计算Mesh对象的邻接信息:
std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3);
Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]);
然后,执行优化:
hr = Mesh->OptimizeInplace(
D3DXMESHOPT_ATTRSORT|D3DXMESHOPT_COMPACT|D3DXMESHOPT_VERTEXCACHE,
&adjacencyBuffer[0],
0, 0, 0);
到这里,创建Mesh对象的工作已经完成了。最后,如果一切顺利,函数Setup应该返回true:
return true;
} // end Setup()
上面说过,经过优化,Mesh对象将建立属性表。使用下面的代码验证一下。
// number of entries in the attribute table
DWORD numEntries = 0;
mesh->GetAttributeTable(0, &numEntries);
vector<D3DXATTRIBUTERANGE> table(numEntries);
mesh->GetAttributeTable(&table[0], &numEntries);
for(int i = 0; i < numEntries; i++)
{
cout << "Entry " << i << endl;
cout << "------" << endl;
cout << "Subset ID: " << table.AttribId << endl;
cout << "Face Start: " << table.FaceStart << endl;
cout << "Face Count: " << table.FaceCount << endl;
cout << "Vertex Start: " << table.VertexStart << endl;
cout << "Vertex Count: " << table.VertexCount << endl;
cout <<endl;
}
下面是部分输出信息:
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
与前面的设计相符。最后,渲染Mesh对象:
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,0x00000000, 1.0f, 0);
Device->BeginScene();
for(int i = 0; i < NumSubsets; i++)
{
Device->SetTexture( 0, Textures );
Mesh->DrawSubset( i );
}
Device->EndScene();
Device-> resent(0, 0, 0, 0);
10.10. 总结 l Mesh对象包含顶点、索引、属性缓冲区。顶点和索引缓冲区包含Mesh对象的几何信息。属性缓冲区的每一项对应一个Mesh对象的一个三角形,指示该三角形所属的子集。
l Mesh对象可以使用OptimizeInplace和Optimize方法优化。为了更加有效的渲染,优化将重组Mesh的几何信息。使用D3DXMESHOPT_ATTRSORT标志进行优化,会产生属性表,据此,访问一次属性表,就可以完整的渲染一个子集。
l Mesh对象的邻接信息是一个DWORD数组,每个三角形有三个邻接项,代表与该三角形相邻接的三角形。
l 可以使用D3DXCreateMeshFVF函数创建空的Mesh对象。然后使用相应的Lock*方法填写适当的数据组成Mesh的几何信息和属性信息。
|