DirectX学习笔记(十一):网格内部原理和方法实现专场

来源:互联网 发布:解谜游戏g5知乎 编辑:程序博客网 时间:2024/06/03 18:40

网格的原理:


我们在前面已经知道,一个物体可以用三角形单元来逼近表示。而网格是由一个或者多个子集构成,这些子集是网格中一组可以用相同属性进行绘制的三角形单元(属性相同是指:材质、纹理和绘制状态)


或者你可以不太明白,那么看下图:


要想知道每个三角形是属于哪个子集的,就必须给每个三角形一个ID,这个ID就是指的这个三角形所属子集。例如:从上图中可以看出,如果一个三角形的ID为0,那么就是属于子集0(即:地板),如果ID为1,那么该三角形就是属于子集1(即:墙)。


说的很清楚,那么三角形单元的ID如何在计算机中存储呢?对于这些数据,计算机将其存储在属性缓存中的。属性缓存的实质就是一个DWORD数组,由于每个三角形单元的ID都存储在属性缓存中,那么属性缓存中的项一一对应每个三角形单元,也就是说三角形单元的个数等于属性缓存中的数据个数。而且,属性缓存中的项与索引缓存中定义的三角形单元是一一对应的。因为索引缓存中每连续三个数据构成一个三角形单元,也就是说一个属性缓存的数据对应三个索引缓存数据:即属性缓存的第i项对应于索引缓存中的第i个三角形:

A  = i * 3;

B = i * 3 + 1;

C = i * 3 + 2;

下图解释了这种关系:




那么这种技术该怎么使用?或者说有什么好处?我们为什么要这样用?


我的粗浅理解就是:我们这样做的目的就是为了方便绘制。ID3DXMesh接口提供了绘制一个子集的所有三角形单元的方法:如:绘制子集0:

Mesh->DrawSubset(0);如果要绘制一个网格,就必须绘制所有的子集。那么前面所说,一个子集的材质、纹理、绘制状态是一样的,也就是说:一个子集和一组属性是一一对应的。这样,我们将各个子集的属性ID依次设置为0,1,2......n-1。n为子集总数。每个子集对应一组纹理、材质、绘制状态,我们用索引i

就可以找到与子集i所对应的纹理和材质,这种方式使得我们使用一个简单的循环就完成了整个网格的绘制。


for (int i = 0; i < Numsubsets; i++){Device->SetTexture(0, Textures[i]);        mesh->DrawSubset(i);}






网格优化:


上面说的那么多枯燥的话,现在来说的有意思的东西。

为了更高效的绘制一个网格(高效的东西总是令人追求和喜爱),我们可以对网格内的顶点和索引进行重组,这个重组的过程就是网格优化。

优化的方法有很多:从网格内移除那些无用的顶点和索引数据、提高顶点高速缓存的命中率、对索引缓存进行优化忽略顶点等等。这些我在此都不讲,我们只讲:

属性表优化方式


优化机理:


属性表优化方式是指:我们依据属性对各三角形单元进行排序,并且生成了一个属性表。这样可以使得DrawSubset获得更高的绘制效率。

我们让构成该网格的三角形依据其属性进行排序,那么,相同属性的三角形就位于了一块连续的存储空间内,也就是说:术语特定子集的三角形单元就会被保存在顶点缓存或者索引缓存中的一个连续的存储空间内。

看下图理解:




构成网格的三角形和属性缓存中的内容都依据了属性进行排序,这样属于特定子集的三角形就会位于一块连续的存储空间内。使得我们可以很容易的标出一个子集的三角形单元在存储空间中的起始位置和结束位置。如上图:子集0包含了三个三角形:0,1,2。子集1包含了三角形:3,4,5,6,7。(上图中的Tri并不是指的点,而是三角形单元)


这样做的意义何在呢?


我们先说如果绘制的时候按照原来的方式:

我们知道,刚开始所有的三角形单元存储是杂乱无章的存储在一块存储空间内。那么,如果我们想在绘制子集0的三角形单元,首先我们需要遍历一遍该存储空间,找出ID为0的所有三角形单元进行绘制。这种方法的效率无疑是低下的。因为其中做了很多不需要的判断和查找,而且需要遍历所有数据。


那么,如果我们按照属性表优化方式:

所有相同属性的三角形单元都存储在连续的空间内,这样我们如果绘制子集0,就不需要遍历所有的数据进行查找,因为它们位于连续的空间内,省去了很多的查找。这样做,虽然看起来没多大卵用。但是如果数据大了呢?这种方式无疑会很大程度的提高绘制效率。而且敲代码的原则就是:尽量优化。


代码实现:


ok,原理都讲完了,现在来说实现:

由于代码太长就不粘贴了。而且觉得一些函数、接口的参数实在无意义,我把接口或者所用到的函数给出来,大家自行查看API。我会在文章末尾提供完整下载链接,完整代码附有要点注释。

总得来说步骤大致如下:

先给出全局变量:

IDirect3DDevice9 * Device = 0;  //主设备ID3DXMesh* mesh = 0;             //网格const DWORD Numsubsets = 3;     //网格子集个数IDirect3DTexture9* Textures[3] = { 0, 0, 0 }; //纹理存储



1.创建空的网格:

HRESULT hr = 0;hr = D3DXCreateMeshFVF(  //创建网格12,24,D3DXMESH_MANAGED,Vertex::FVF,Device,&mesh);


2.将数据写入:

(1)顶点缓存:

Vertex* v = 0;mesh->LockVertexBuffer(0, (void**)&v);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[2] = Vertex(1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);v[3] = Vertex(1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);..............................................................mesh->UnlockVertexBuffer();


(2)索引缓存:


WORD* i = 0;mesh->LockIndexBuffer(0, (void**)&i);i[0] = 0; i[1] = 1; i[2] = 2;i[3] = 0; i[4] = 2; i[5] = 3;i[6] = 4; i[7] = 5; i[8] = 6;i[9] = 4; i[10] = 6; i[11] = 7;...................................mesh->UnlockIndexBuffer();


3.指定网格中每个片面所属的子集:


本例中,我们制定索引缓存中的前四个三角形单元属于子集0,中间4个属于子集1,最后的属于子集2.:

//属性缓存DWORD* attributeBuffer = 0;mesh->LockAttributeBuffer(0, &attributeBuffer);for (int a = 0; a < 4; a++)attributeBuffer[a] = 0;for (int b = 4; b < 8; b++)attributeBuffer[b] = 1;for (int c = 8; c < 12; c++)attributeBuffer[c] = 2;mesh->UnlockAttributeBuffer();


4.生成该网格的邻接信息:

对于网格的一些运算,必须要用到邻接信息,包括网格优化。需要知道对于给定的三角形,哪些三角形与其相邻。这些信息被存储在邻接数组中。邻接数组的类型为DWORD,其中每一项包括了一个标识网格中某个三角形的索引。例如:第i项是由下列顶点构成的三角形单元:
A = i * 3;

B = i * 3 + 1;

C = i * 3 + 2;

至于这个邻接信息到底如何用的,博主现在还不太了解。等知道了,立马更新。

//计算该网格的邻接信息:std::vector<DWORD> adjacencyBuffer(mesh->GetNumFaces() * 3);mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]);

5.对网格进行优化:

hr = mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, &adjacencyBuffer[0], 0, 0, 0);

6.绘制网格:

for (int i = 0; i < Numsubsets; i++){Device->SetTexture(0, Textures[i]);mesh->DrawSubset(i);}

总的来说:

1.创建空的网格对象。

2.将数据写入网格缓存中

3.指定网格内每个三角形单元所属的子集

4.生成该网格的邻接信息

5.优化网格。

6.绘制网格


代码运行效果:



代码下载链接:点击打开链接

有问题的欢迎留言交流,一起努力一起进步。


1 0
原创粉丝点击