分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/zhmxy555/article/details/8304741
作者:毛星云(浅墨) 邮箱: happylifemxy@163.com
本篇文章里,我们首先对索引缓存的相关概念进行了详细的剖析,然后介绍了索引缓存配合顶点缓存一起绘制图形的具体方法,最后依旧是提供文章配套的详细注释的源代码的欣赏,并在文章末尾提供了源代码下载。
在上篇文章《【Visual C++】游戏开发笔记三十六 浅墨DirectX提高班之四 顶点缓存的逆袭》里我们介绍了用顶点缓存来玩转Direct3D,绘制图形的方法。但是,在使用过程中我们会发现,当我们要绘制复杂几何体的时候,单单地运用顶点缓存来存储顶点数据的话,就显得有些死板和力不从心了。首先呢,让我们来举一些引例,证明我们提出的这个论点,引出这篇文章的主角。
一.引言
第一个例子。比如,我们现在要用顶点缓存绘制一个正方形。首先我们知道,三角形是Direct3D中绘制图形的基本单元,我们绘制任何图形,说白了,就是用大量的三角形组合起来,堆砌完成的。而正方形,显然是由两个大小相同的三角形结合起来组成的。所以要绘制一个正方形,我们用顶点缓存写两个三角形,然后进行绘制就可以了。而一个三角形有三个顶点,两个三角形就有六个顶点。所以,用顶点缓存绘制一个正方形的话,需要用六个顶点缓存。而众所周知一个正方形也就是四个顶点。也就是说我们单用顶点缓存来绘制一个正方形,多用了两个顶点。下面就是我们在上篇文章中用到的配图:
其实单单用顶点缓存来绘制图形的话,需要的个数是非常好算的,我们只要数图形中三角形的个数,然后把这个个数乘以3就可以了。
第二个例子,八边形的绘制。如下图,单单用顶点缓存来绘制八边形的话,需要3*8=24个顶点缓存。
代码方面,我们就可以这样写:
Vertex circle[24] = { v0, v1, v2, // Triangle 0 v0, v2, v3, // Triangle 1 v0, v3, v4, // Triangle 2 v0, v4, v5, // Triangle 3 v0, v5, v6, // Triangle 4 v0, v6, v7, // Triangle 5 v0, v7, v8, // Triangle 6 v0, v8, v1 // Triangle 7};
第三个例子,立方体的绘制。如下图
我们知道,一个立方体六个面,每个面都为一个正方形,所以一共有2*6=12个三角形,所以就有12*3=36个顶点。则单单用顶点缓存来完成这个立方体的话,就需要36个顶点缓存。
一个立方体只有8个顶点,我们的想法是每个顶点我们只要记录一次,这样就可以节约我们的资源开销。但是我们采用的这种方式,却需要36个顶点,每个顶点都多存储了3.5次,这不科学。
通过上面的几个引例我们可以发现,这种单单用顶点缓存来绘制图形的方法在应对复杂图形的时候非常不科学,显得复杂而力不从心。
也就是说,当物体模型很复杂、顶点数量很大时,仅使用顶点缓存绘制图形会使重复的顶点大大增加,并且Direct3D仍需要对这些重复的顶点进行计算,因此需要更多的存储空间和更大的开销。
这时候,我们顶点缓存的红颜知己——索引缓存是时候出场了。
索引缓存((Index Buffers)),人如其名,它就是一个索引,用于记录顶点缓存中每一个顶点的索引位置。我们可以把索引缓存理解为是一本书(这本书就是顶点缓存)的目录,一本书有了目录,我们才能高屋建瓴,更快更高效地去阅读和掌握这本书。索引缓存作为顶点缓存的目录和提纲,能让顶点缓存发挥出内在的潜力,更高效更容易地绘制出一个3D图形来。
另外提一点,索引缓存能够加速显存中快速存取顶点数据位置的能力。
二、索引缓存的使用思路
顶点缓存保存了物体模型所有的顶点数据,而这些数据可以是唯一的。索引缓存保存了构成物体的顶点在顶点缓存的索引值,通过索引查找对应的顶点,以完成图形的绘制。
下面我们看看上篇文章里我们介绍顶点缓存时贴出来过的绘制一个正方形需要的思路图的升级版,这幅图是采用顶点缓存和索引缓存双剑合璧的方法来绘制的,可以和文章开头举得第一个例子的图对照起来看:
我们可以看到,在这幅图中我们用4个顶点和6个索引来描述一个正方形。
我们在顶点缓存中只需要保存这四个顶点就可以了,而绘制正方形的两个三角形△V0V1V2和△V0V2V3则通过索引缓存来表示。
关于上面讲到的知识,我们再不厌其烦地总结起来一遍:
当物体模型很复杂、顶点数量很大时,仅使用顶点缓存绘制图形会使重复的顶点大大增加,并且Direct3D仍需要对这些重复的顶点进行计算,因此需要更多的存储空间和更大的开销。但是如果我们把顶点缓存和他的好兄弟索引缓存配合起来使用的话,就可以化繁为简,化腐朽为神奇,不仅我们代码敲起来轻松了很多,也让我们写出来的程序性能可以很高。越复杂的图形,越体现了索引缓存的价值。不过需要注意的是,我们后面使用Direct3D绘制的物体,大多数情况下是从3D模型文件中载入的,而不是在Direct3D中用代码敲出来的。
三、相濡以沫的顶点缓存与索引缓存
顶点缓存与索引缓存之间的关系,宛如俞伯牙与钟子期那种高山流水般的知己情谊,也如千里马与伯乐的那种难得的知遇之恩。
为什么这样说呢,那得看一看在使用顶点缓存配合索引缓存绘制图形时,用到的IDirect3DDevice9::DrawIndexedPrimitive函数了。首先,我们得注意下这个函数的拼写,浅墨发觉经常有朋友会错成DrawIndexPrimitive,注意其中Index后面有个ed,千万不要忘了。正确写法是DrawIndexedPrimitive。这个函数其实和使用顶点缓存绘制图形时用到的IDirect3DDevice9::DrawPrimitive函数用法非常相似,有个别参数基本上一样。我们可以在VS2010的MSDN中查到这个函数有如下原型:
HRESULT DrawIndexedPrimitive( [in] D3DPRIMITIVETYPE Type, [in] INT BaseVertexIndex, [in] UINT MinIndex, [in] UINT NumVertices, [in] UINT StartIndex, [in] UINT PrimitiveCount);
█ 第一个参数,D3DPRIMITIVETYPE类型的Type,这个参数和DrawPrimitive方法中第一个参数一摸一样,表示将要绘制的图元类型,在D3DPRIMITIVETYPE枚举体中取值就可以了,其中D3DPRIMITIVETYPE枚举体定义如下:
typedef enum D3DPRIMITIVETYPE { D3DPT_POINTLIST = 1, D3DPT_LINELIST = 2, D3DPT_LINESTRIP = 3, D3DPT_TRIANGLELIST = 4, D3DPT_TRIANGLESTRIP = 5, D3DPT_TRIANGLEFAN = 6, D3DPT_FORCE_DWORD = 0x7fffffff } D3DPRIMITIVETYPE, *LPD3DPRIMITIVETYPE;
其中D3DPT_POINTLIST表示点列,D3DPT_LINELIST表示线列,D3DPT_LINESTRIP表示线带,D3DPT_TRIANGLELIST表示三角形列,D3DPT_TRIANGLESTRIP表示三角形带,D3DPT_TRIANGLEFAN表示三角形扇元,而最后一个D3DPT_FORCE_DWORD不用去理,表示将顶点缓存强制编译为32位,这个参数目前不使用。
█ 第二个参数,INT类型的BaseVertexIndex,表示将要进行绘制的索引缓存的起始顶点的索引位置,也就是我们从哪个顶点开始做我们的索引目录,或者说是索引缓存区引用起始位置对应的顶点位置。
█ 第三个参数,UINT类型的MinIndex,表示索引数组中最小的索引值,通常都设为0,这样我们的索引就是0,1,2,3,4,5这样往后排的。
█ 第四个参数,UINT类型的NumVertices,表示我们这次调用DrawIndexedPrimitive方法所需要的顶点个数,也就是为多少个顶点做索引,或者说是索引缓存中使用的顶点数目。
█ 第五个参数,UINT类型的StartIndex,表示从索引中的第几个索引处开始绘制我们的图元,或者说是索引缓存区开始读索引值的起始位置。
█ 第六个参数,UINT类型的PrimitiveCount,显然就是要绘制的图元个数了。
有些知识点真的是很难用文字表达出来的,正所谓有图有真相。为了便于大家的理解,浅墨依旧是配了一副关于DrawIndexedPrimitive方法,顶点缓存和索引缓存之间的关系图:
在DrawIndexedPrimitive中我们有参数NumVertices指定索引缓存中使用的顶点数目,以及参数StartIndex指定起始索引位置,使我们在使用顶点缓存和索引缓存时更加如鱼得水。比如我们在顶点缓存区保存了多个物体模型的顶点数据,那么就可以在每次调用DrawIndexedPrimitive方法时,通过制定顶点、顶点数和起始索引,可以分别绘制出顶点缓存与索引缓存中存储着的不同物体的模型。
这样,我们就可以用一段顶点缓存区配合一段索引缓存区,应付多样化的物体顶点的保存和绘制。
通过上面的演绎我们可以发现,索引缓存是最懂顶点缓存的知己了,他可以把顶点缓存的潜力无限地挖掘,让顶点缓存可以用最少的“能量”,迸发出最大的“光亮”来。一如俞伯牙与钟子期那种高山流水般的知己情谊,也如千里马与伯乐的那种难得的知遇之恩。
浅墨在想,如果顶点缓存是个人的话,它一定会发出感叹。
知我者,索引缓存也。
最后,做个总结吧。
索引缓存,就是为了辅佐顶点缓存更好更简洁更高效更有序地绘制图形而存在的。
没有顶点缓存,单单存在索引缓存是没有意义的。
顶点缓存和索引缓存有点相濡以沫的感觉,需要一起使用,双剑合璧,才能迸发出无穷无尽的能量~
四、双剑合璧:顶点缓存、索引缓存使用四步曲
上面我们讲过,索引缓存单独用起来是没有意义的,需要配合顶点缓存一起使用,下面我们就看一起学习,顶点缓存和索引缓存是如何搭配起来使用的。其实顶点缓存和索引缓存的创建和访问以及绘制过程是非常相似的,相信有了上篇文章中的讲解,来理解下面的这些知识点是毫无问题的。
Ⅰ.顶点、索引缓存绘图四步曲之一:设计顶点格式
这一步的关键词是设计,Design。
其实这一步和上一篇文章《顶点缓存的逆袭》中的内容完全一致,也就设计出合适的顶点格式,浅墨在这里就只是贴出这步的实现代码:
//*****************************************************************************************// 【顶点缓存、索引缓存绘图四步曲之一】:设计顶点格式//*****************************************************************************************struct CUSTOMVERTEX{ FLOAT x, y, z,rhw; DWORD color;};#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) //FVF灵活顶点格式
Ⅱ. 顶点、索引缓存绘图四步曲之二:创建顶点缓存以及索引缓存
这一步的关键词是创建,create。
上篇文章中我们讲过,在Direct3D中,顶点缓存由IDirect3DVertexBuffer9接口对象来表示的。而本篇文章的主角索引缓存的对象,则由IDirect3DIndexBuffer9来表示的。我们可以看到这两个接口命名规则非常的相似。其实,这两个接口中的方法也基本相同,都是GetDesc,Lock和Unlock三个。
这一步里面我们就是先分别定义指向IDirect3DVertexBuffer9接口和IDirect3DIndexBuffer9接口的的指针变量,然后分别运用IDirect3DVertexBuffer9接口的CreateVertexBuffer方法和CreateIndexBuffer方法创建顶点缓存和索引缓存,把内存地址复制分别复制给我们创建的这两个指针。
首先,分别定义指向IDirect3DVertexBuffer9接口和IDirect3DIndexBuffer9接口的的指针变量。即写上这么两句:
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL; //顶点缓存对象LPDIRECT3DINDEXBUFFER9 g_pIndexBuf = NULL; // 索引缓存对象