`
nanjingjiangbiao_T
  • 浏览: 2586821 次
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

【Visual C++】游戏开发笔记三十七 浅墨DirectX提高班之五 顶点缓存的红颜知己:索引缓存的故事

 
阅读更多

本系列文章由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;    // 索引缓存对象

然后就是分别运用IDirect3DVertexBuffer9接口的CreateVertexBuffer方法和CreateIndexBuffer方法创建顶点缓存和索引缓存,把内存地址复制分别复制给我们创建的这两个指针。

其实这两个方法参数和使用方法八九不离十,都是一样的,只有一点细微的差别。这里我们贴出两者的原型,然后对今天的主场作战的CreateIndexBuffer做下详细讲解。首先是贴出创建顶点缓存的方法CreateVertexBuffer的原型(参数讲解在上篇文章里):

HRESULT CreateVertexBuffer(
  [in]           UINT Length,
  [in]           DWORD Usage,
  [in]           DWORD FVF,
  [in]           D3DPOOL Pool,
  [out, retval]  IDirect3DVertexBuffer9 **ppVertexBuffer,
  [in]           HANDLE *pSharedHandle
);


然后是今天的主场作战的CreateIndexBuffer的原型和参数讲解:

HRESULT CreateIndexBuffer(
  [in]           UINT Length,
  [in]           DWORD Usage,
  [in]           D3DFORMAT Format,
  [in]           D3DPOOL Pool,
  [out, retval]  IDirect3DIndexBuffer9 **ppIndexBuffer,
  [in]           HANDLE *pSharedHandle
);


█ 第一个参数,UINT类型的Length,表示索引缓存的大小,以字节为单位。

█ 第二个参数,DWORD类型的Usage用于指定使用缓存的一些附加属性,这个参数的只可以取0,表示没有附加属性,或者取如下表格中的一个或者多个值,多个值之间用“|”连接。这些参数依然是可以顾名思义,不用记忆。比如D3DUSAGE_WRITEONLY,显然表示的是只能写,不能读。

缓存区属性

说明

D3DUSAGE_DONOTCLIP

表示禁用裁剪,即顶点缓冲区中的顶点不进行裁剪,当设置这个属性时,渲染状态D3DRS_CLIPPING必须设为FALSE

D3DUSAGE_DYNAMIC

表示使用动态缓存

D3DUSAGE_NPARCHES

表示使用顶点缓存绘制这种N-patches曲线

D3DUSAGE_POINTS

表示使用顶点缓存绘制点

D3DUSAGE_RTPATCHES

表示使用顶点缓存绘制曲线

D3DUSAGE_SOFTWAREPROCESSING

表示使用软件来进行顶点运算,不指定这个值的话,就取的是默认方式——硬件顶点运算

D3DUSAGE_WRITEONLY

顾名思义,只能进行写操作,不能进行读操作,设置这个属性可以提高系统性能

█第三个参数,D3DFORMAT类型的Format,我们可以发现这个参数是CreateIndexBuffer和CreateVertexBuffer最大的不同点。这个参数用于指定索引缓存中存储每个索引的大小,可以看到他为D3DFORMAT枚举类型。D3DFORMAT这个枚举类型中内容有些多,在这里就不贴出来了,具体可以去查查MSDN,我们在这里只讲一下常常会用到的值。取值为D3DFMT_INDEX16表示为16位的索引,取值为D3DFMT_INDEX32则表示为32位的索引。通常我们都用的是16位的索引,也就是取值D3DFMT_INDEX16。

第四个参数,D3DPOOL类型的Pool,这是一个D3DPOOL枚举类型,在这里用来指定存储索引缓存的存储位置是在内存中还是在显卡的显存中。默认情况下是在显卡的显存中的。这个枚举类型的原型是这样的:

typedef enum D3DPOOL {
  D3DPOOL_DEFAULT       = 0,
  D3DPOOL_MANAGED       = 1,
  D3DPOOL_SYSTEMMEM     = 2,
  D3DPOOL_SCRATCH       = 3,
  D3DPOOL_FORCE_DWORD   = 0x7fffffff 
} D3DPOOL, *LPD3DPOOL; 


其中每个值的含义如下:

枚举值

精析

D3D3POOL_DEFAULT

默认值,表示顶点缓存存在于显卡的显存中

D3D3POOL_MANAGED

由Direct3D自由调度顶点缓冲区内存的位置(显存或者缓存中)

D3DPOOL_SYSTEMMEM

表示顶点缓存位于内存中

D3DPOOL_SCRATCH

表示顶点缓冲区位于临时内存当中,这种类型的顶点缓存去不能直接进行渲染,只能进行内存加锁和复制的操作

D3D3POOL_FORCE_DWORD

表示将顶点缓存强制编译为32位,这个参数目前不使用。

█第五个参数,IDirect3DIndexBuffer9类型的**ppIndexBuffer,我们可以这样理解,调用CreateIndexBuffer方法就是在对这个变量进行初始化,让他得到创建好的索引缓存的地址。这个参数也是一把金钥匙,后面我们关于索引缓存的操作,都是以他为媒的,拿着它做喜闻乐见的“->”操作就可以了。我们可以看到,它的类型有两个星号,为指针的指针。而我们之前定义的g_pIndexBuffer参数骨子里只有一个星号,为单纯的指针而已,所以这里我们需要做一下取地址操作。

█第六个参数,HANDLE类型的*pSharedHandle,为保留参数,我们一般不用去管他,设为NULL或者0就万事大吉了。

所以这一步综合起来,就是这样写:

	//--------------------------------------------------------------------------------------
	   // 【Direct3D渲染五步曲之二】:开始绘制
	   //--------------------------------------------------------------------------------------
	   g_pd3dDevice->BeginScene(); // 开始绘制
	   //--------------------------------------------------------------------------------------
	   // 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
	   //--------------------------------------------------------------------------------------
	
	   //--------------------------------------------------------------------------------------
	   // 【顶点缓存、索引缓存绘图四步曲之四】:绘制图形
	   //--------------------------------------------------------------------------------------
	   // 设置渲染状态
	   g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE,D3DSHADE_GOURAUD); //这句代码可省略,因为高洛德着色模式胃D3D默认的着色模式
	
	   g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) );//把包含的几何体信息的顶点缓存和渲染流水线相										关联
	   g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );//指定我们使用的灵活顶点格式的宏名称
	   g_pd3dDevice->SetIndices(g_pIndexBuf);//设置索引缓存
	   g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 17, 0, 16);//利用索引缓存配合顶点缓存绘制图形
	   //--------------------------------------------------------------------------------------
	   // 【Direct3D渲染五步曲之四】:结束绘制
	   //--------------------------------------------------------------------------------------
	   g_pd3dDevice->EndScene(); // 结束绘制


Ⅲ. 顶点、索引缓存绘图四步曲之三:访问顶点缓存以及索引缓存

这一步的关键词是访问,Access。

与访问顶点缓存的方式相似,在访问索引缓存时需要调用IDirect3DIndexBuffer接口的Lock方法对缓存进行加锁,并取得加锁后的缓存首地址,然后通过该地址向索引缓存中写入索引信息,最后调用Unlock方法对缓存进行解锁。

IDirect3DIndexBuffer接口中的加锁和解锁函数和IDirect3DVertexBuffer接口中的加锁和解锁函数原型和用法完全一致,在这里浅墨再贴出来一遍:

HRESULT Lock(
  [in]   UINT OffsetToLock,
  [in]   UINT SizeToLock,
  [out]  VOID **ppbData,
  [in]   DWORD Flags
);

█第一个参数,UINT类型的OffsetToLock,表示加锁区域自存储空间的起始位置到开始锁定位置的偏移量,单位为字节。

█第二个参数,UINT类型的SizeToLock,表示要锁定的字节数,也就是加锁区域的大小。

█第三个参数,VOID类型的**ppbData,指向被锁定的存储区的起始地址的指针。

█第四个参数,DWORD类型的Flags,表示锁定的方式,我们可以把它设为0,也可以为下面的之一或者组合:

Flags取值

精析

D3DLOCK_DISCARD

这个标记只能用于动态缓存,表示硬件将缓存内容丢弃,并且返回一个指向重新分配的缓存的指针。这个标记比较好用,比如我们在访问新分配的内存时,硬件依然能够继续使用被丢弃的缓存中的数据进行绘制,这样硬件的绘制就不会停止

D3DLOCK_NOOVERWRITE

字面上理解为不能覆盖。这个标记也只能用于动态缓存中,使用这个标记后,数据只能以追加的方式写入缓存。顾名思义,我们不能覆盖当前用于绘制的存储区中的任何内容。这个标记保证了我们在缓存中添加新的数据的时候,硬件依然可以进行绘制

D3DLOCK_READONLY

字面上理解为只读,这个标记表示对于我们锁定起来的缓存只能读而不能写,一般我们用这个标示来进行一些内容的优化

讲完Lock函数,对应的当然要讲下UnLock函数,但Unlock函数真的没什么好讲的- -,因为他没有参数,简单的调用一下就可以了。就像这样:

g_pIndexBuf->Unlock();


这里主要有两种方式来访问缓存的内容,下面我们依旧分别介绍:

第一种方式是直接在Lock和UnLock之间对每个索引的数据进行赋值和修改,以Lock方法中的ppbData指针参数作为数组的首地址,例如这样写:

// 填充索引数据
                             WORD *pIndices = NULL;
                            g_pIndexBuf->Lock(0, 0, (void**)&pIndices, 0);
                            pIndices[0] = 0, pIndices[1] = 2, pIndices[2] = 3;                   
			    pIndices[3] = 2, pIndices[4] = 1, pIndices[5] = 3;
                            pIndices[6] = 1, pIndices[7]  = 0, pIndices[8]= 3;
                            pIndices[9] = 2, pIndices[10] = 0, pIndices[11] = 1
                            g_pIndexBuf->Unlock();

第二种方式是事先准备好索引数据的数组,然后在Lock和Unlock之间用下memcpy函数,进行数组内容的拷贝就可以了。例如这样写:

//顶点索引数组
            WORD Indices[] ={ 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,1 };// 填充索引数据
            WORD *pIndices = NULL;
             g_pIndexBuf->Lock(0, 0, (void**)&pIndices, 0);
             memcpy( pIndices, Indices, sizeof(Indices) );
             g_pIndexBuf->Unlock();


所以,这一步里面代码整体来看,就是这样的,这段代码依旧本节配套源码里面的,绘制一个彩色的十六边形:

	//--------------------------------------------------------------------------------------
		// 【顶点缓存、索引缓存绘图四步曲之三】:访问顶点缓存和索引缓存
		//--------------------------------------------------------------------------------------
		//顶点数据的设置,
		CUSTOMVERTEX Vertices[17];
		Vertices[0].x = 400;
		Vertices[0].y = 300;
		Vertices[0].z = 0.5f;
		Vertices[0].rhw = 1.0f;
		Vertices[0].color = D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		for(int i=0; i<16; i++)
		{
			Vertices[i+1].x =  (float)(250*sin(i*3.14159/8.0)) + 400;
			Vertices[i+1].y = -(float)(250*cos(i*3.14159/8.0)) + 300;
			Vertices[i+1].z = 0.5f;
			Vertices[i+1].rhw = 1.0f;
			Vertices[i+1].color =  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		}

		//填充顶点缓冲区
		VOID* pVertices;
		if( FAILED( g_pVertexBuffer->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 ) ) )
			return E_FAIL;
		memcpy( pVertices, Vertices, sizeof(Vertices) );
		g_pVertexBuffer->Unlock();

		//索引数组的设置
		WORD Indices[] ={ 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,9, 0,9,10, 0,10,11 ,0,11,12, 0,12,13 ,0,13,14 ,0,14,15 ,0,15,16, 0, 16,1 };

		// 填充索引数据
		WORD *pIndices = NULL;
		g_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
		memcpy( pIndices, Indices, sizeof(Indices) );
		g_pIndexBuffer->Unlock();


Ⅳ.顶点、索引缓存绘图四步曲之四:绘制图形

最后一步的关键词是绘制,Draw。

因为我们的顶点缓存和索引缓存是配合使用的,这里就和单用顶点缓存绘制不太一样了。单用顶点缓存绘制,需要用到IDirect3DDevice9::SetStreamSource,IDirect3DDevice9::SetFVF,IDirect3DDevice9::DrawPrimitive这三个方法,也就是这样写(单用顶点缓存绘制):

        g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) ); 
        g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); 
        g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 ); 


而使用索引缓存绘制图形时,在用完IDirect3DDevice9::SetStreamSource方法把包含的几何体信息的顶点缓存和渲染流水线相关联,以及IDirect3DDevice9::SetFVF方法指定我们使用的灵活顶点格式的宏名称之后,还要调用IDirect3DDevice9接口的一个SetIndices方法设置索引缓存,最后才是调用绘制函数,而这里的绘制函数不是我们上篇文章里使用的DrawPrimitive了,而是我们在本篇文章第三部分里重点讲过的DrawIndexedPrimitive。

由于SetStreamSource和SetFVF上篇文章里有详细讲到,而第三部分里有详细讲过DrawIndexedPrimitive函数,所以唯一落单的就是我们的SetIndices方法了。下面我们先贴出这四个方法的原型,然后重点讲解下SetIndices方法。

首先是要调用的第一个函数SetStreamSource的原型:

HRESULT SetStreamSource(
  [in]  UINT StreamNumber,
  [in]  IDirect3DVertexBuffer9 *pStreamData,
  [in]  UINT OffsetInBytes,
  [in]  UINT Stride
);


然后是第二个调用的,SetFVF函数的原型:

HRESULT SetFVF(
  [in]  DWORD FVF
);

然后是第三个调用的函数SetIndices的原型和讲解:

HRESULT SetIndices(
  [in]  IDirect3DIndexBuffer9 *pIndexData
);

这个函数唯一的参数是IDirect3DIndexBuffer9类型的*pIndexData,指向我们设置的索引缓冲区的地址,就是点名要我们的那把金钥匙,第二步里我们定义的并初始化的那个g_pIndexBuf。

最后是绘制函数,DrawIndexedPrimitiveIndex方法,原型如下:

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的原型声明,本篇文章第三部分有完整版的介绍)其中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,显然就是要绘制的图元个数了。

所以,这一步综合起来看,就是如下的代码:

//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
	//--------------------------------------------------------------------------------------

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之四】:绘制图形
	//--------------------------------------------------------------------------------------
	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE,D3DSHADE_GOURAUD);  //这句代码可省略,因为高洛德着色模式胃D3D默认的着色模式

	g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) );//把包含的几何体信息的顶点缓存和渲染流水线相关联
	g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );//指定我们使用的灵活顶点格式的宏名称
	g_pd3dDevice->SetIndices(g_pIndexBuffer);//设置索引缓存
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 17, 0, 16);//利用索引缓存配合顶点缓存绘制图形


	//在窗口右上角处,显示每秒帧数
	int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );
	g_pFont->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_XRGB(255,239,136));

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制

Ⅴ.总

最后总结一下,使用索引缓存配合顶点缓存来绘制图形,核心思想没变,大体思路没变,总结起来,简明扼要,依旧是四步曲,依旧是八个字:

设计,创建,访问,绘制

五、详细注释的源代码欣赏

下面依旧是通过一个小程序,来把本篇文章所学的知识融会贯通。这篇文章里我们讲解了利用顶点缓存的红颜知己索引缓存配合顶点缓存一起来绘制图形,下面就是一个索引缓存配合顶点缓存绘制一个彩色的随机顶点颜色的十六边形的demo的详细注释源代码(文章末尾有源代码的下载地址)

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码 三十七 浅墨DirectX提高班之五 顶点缓存的红颜知己:索引缓存的故事
//		 VS2010版
// 2012年 12月16日  Create by 浅墨 
//图标素材: Dota2 黑暗游侠的大招 射手天赋
//此刻心情:耿耿于怀着过去和忐忑不安着未来的人,也常常挥霍无度着现在。希望我们都做那个把握好现在的人。
//
//***************************************************************************************** 




//*****************************************************************************************
// Desc: 头文件定义部分  
//*****************************************************************************************                                                                                       
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h>
#include   <time.h> 




//*****************************************************************************************
// Desc: 库文件定义部分  
//***************************************************************************************** 
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib, "winmm.lib ")


//*****************************************************************************************
// Desc: 宏定义部分   
//*****************************************************************************************
#define SCREEN_WIDTH	800						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define SCREEN_HEIGHT	600							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	_T("【Visual C++游戏开发笔记】博文配套demo之三十七 浅墨DirectX提高班之五 顶点缓存的红颜知己:索引缓存的故事") //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }      //自定义一个SAFE_RELEASE()宏,便于资源的释放



//*****************************************************************************************
// 【顶点缓存、索引缓存绘图四步曲之一】:设计顶点格式
//*****************************************************************************************
struct CUSTOMVERTEX
{
	FLOAT x, y, z,rhw;
	DWORD color;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)  //FVF灵活顶点格式


//*****************************************************************************************
// Desc: 全局变量声明部分  
//*****************************************************************************************
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; //Direct3D设备对象
ID3DXFont*				g_pFont=NULL;    //字体COM接口
float					g_FPS = 0.0f;       //一个浮点型的变量,代表帧速率
wchar_t					g_strFPS[50];    //包含帧速率的字符数组
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL;    //顶点缓存对象
LPDIRECT3DINDEXBUFFER9  g_pIndexBuffer  = NULL;    // 索引缓存对象

//*****************************************************************************************
// Desc: 全局函数声明部分 
//***************************************************************************************** 
LRESULT CALLBACK	WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
HRESULT				Direct3D_Init(HWND hwnd);
HRESULT				Objects_Init();
void				Direct3D_Render( HWND hwnd);
void				Direct3D_CleanUp( );
float				Get_FPS();


//*****************************************************************************************
// Name: WinMain( )
// Desc: Windows应用程序入口函数
//*****************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{

	//开始设计一个完整的窗口类
	WNDCLASSEX wndClass = { 0 };				//用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化    
	wndClass.cbSize = sizeof( WNDCLASSEX ) ;	//设置结构体的字节数大小
	wndClass.style = CS_HREDRAW | CS_VREDRAW;	//设置窗口的样式
	wndClass.lpfnWndProc = WndProc;				//设置指向窗口过程函数的指针
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance = hInstance;				//指定包含窗口过程的程序的实例句柄。
	wndClass.hIcon=(HICON)::LoadImage(NULL,_T("icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标
	wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口类的光标句柄。
	wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //为hbrBackground成员指定一个灰色画刷句柄
	wndClass.lpszMenuName = NULL;						//用一个以空终止的字符串,指定菜单资源的名字。
	wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");		//用一个以空终止的字符串,指定窗口类的名字。

	if( !RegisterClassEx( &wndClass ) )				//设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
		return -1;		

	HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,			//喜闻乐见的创建窗口函数CreateWindow
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,
		SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );


	//Direct3D资源的初始化,调用失败用messagebox予以显示
	if (!(S_OK==Direct3D_Init (hwnd)))
	{
		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 
	}



	MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处
	ShowWindow( hwnd, nShowCmd );    //调用Win32函数ShowWindow来显示窗口
	UpdateWindow(hwnd);  //对窗口进行更新,就像我们买了新房子要装修一样

	

	

	//消息循环过程
	MSG msg = { 0 };  //初始化msg
	while( msg.message != WM_QUIT )			//使用while循环
	{
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			Direct3D_Render(hwnd);			//调用渲染函数,进行画面的渲染
		}
	}

	UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);
	return 0;  
}



//*****************************************************************************************
// Name: WndProc()
// Desc: 对窗口消息进行处理
//*****************************************************************************************
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc
{
	switch( message )				//switch语句开始
	{
	case WM_PAINT:					 // 客户区重绘消息
		Direct3D_Render(hwnd);          //调用Direct3D_Render函数,进行画面的绘制
		ValidateRect(hwnd, NULL);   // 更新客户区的显示
		break;									//跳出该switch语句

	case WM_KEYDOWN:                // 键盘按下消息
		if (wParam == VK_ESCAPE)    // ESC键
			DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息
		break;
	case WM_DESTROY:				//窗口销毁消息
		Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象
		PostQuitMessage( 0 );		//向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
		break;						//跳出该switch语句

	default:						//若上述case条件都不符合,则执行该default语句
		return DefWindowProc( hwnd, message, wParam, lParam );		//调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。
	}

	return 0;					//正常退出
}


//*****************************************************************************************
// Name: Direct3D_Init( )
// Desc: 初始化Direct3D
// Point:【Direct3D初始化四步曲】
//		1.初始化四步曲之一,创建Direct3D接口对象
//		2.初始化四步曲之二,获取硬件设备信息
//		3.初始化四步曲之三,填充结构体
//		4.初始化四步曲之四,创建Direct3D设备接口
//*****************************************************************************************

HRESULT Direct3D_Init(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
	//--------------------------------------------------------------------------------------
	LPDIRECT3D9  pD3D = NULL; //Direct3D接口对象的创建
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
 			return E_FAIL;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
	//--------------------------------------------------------------------------------------
	D3DCAPS9 caps; int vp = 0;
	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
		{
			return E_FAIL;
		}
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
	//--------------------------------------------------------------------------------------
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.BackBufferWidth            = SCREEN_WIDTH;
	d3dpp.BackBufferHeight           = SCREEN_HEIGHT;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 2;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = true;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = 0;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
	//--------------------------------------------------------------------------------------
	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
		hwnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	if(!(S_OK==Objects_Init())) return E_FAIL;



	SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉

	return S_OK;
}

HRESULT Objects_Init()
{
	//创建字体
	if(FAILED(D3DXCreateFont(g_pd3dDevice, 30, 0, 0, 1, FALSE, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("宋体"), &g_pFont)))
		return E_FAIL;



	srand((unsigned)time(NULL));      //初始化时间种子



	


	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之二】:创建顶点缓存和索引缓存
	//--------------------------------------------------------------------------------------
		//创建顶点缓存
		if( FAILED( g_pd3dDevice->CreateVertexBuffer( 18*sizeof(CUSTOMVERTEX),
			0, D3DFVF_CUSTOMVERTEX,
			D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL ) ) )
		{
			return E_FAIL;
		}
		// 创建索引缓存
	if( FAILED( 	g_pd3dDevice->CreateIndexBuffer(48 * sizeof(WORD), 0, 
		D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pIndexBuffer, NULL)) )
		{
		return E_FAIL;

		}
		//--------------------------------------------------------------------------------------
		// 【顶点缓存、索引缓存绘图四步曲之三】:访问顶点缓存和索引缓存
		//--------------------------------------------------------------------------------------
		//顶点数据的设置,
		CUSTOMVERTEX Vertices[17];
		Vertices[0].x = 400;
		Vertices[0].y = 300;
		Vertices[0].z = 0.5f;
		Vertices[0].rhw = 1.0f;
		Vertices[0].color = D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		for(int i=0; i<16; i++)
		{
			Vertices[i+1].x =  (float)(250*sin(i*3.14159/8.0)) + 400;
			Vertices[i+1].y = -(float)(250*cos(i*3.14159/8.0)) + 300;
			Vertices[i+1].z = 0.5f;
			Vertices[i+1].rhw = 1.0f;
			Vertices[i+1].color =  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		}

		//填充顶点缓冲区
		VOID* pVertices;
		if( FAILED( g_pVertexBuffer->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 ) ) )
			return E_FAIL;
		memcpy( pVertices, Vertices, sizeof(Vertices) );
		g_pVertexBuffer->Unlock();

		//索引数组的设置
		WORD Indices[] ={ 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,9, 0,9,10, 0,10,11 ,0,11,12, 0,12,13 ,0,13,14 ,0,14,15 ,0,15,16, 0, 16,1 };

		// 填充索引数据
		WORD *pIndices = NULL;
		g_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
		memcpy( pIndices, Indices, sizeof(Indices) );
		g_pIndexBuffer->Unlock();

	return S_OK;
}

//*****************************************************************************************
// Name: Direct3D_Render()
// Desc: 进行图形的渲染操作
// Point:【Direct3D渲染五步曲】
//		1.渲染五步曲之一,清屏操作
//		2.渲染五步曲之二,开始绘制
//		3.渲染五步曲之三,正式绘制
//		4.渲染五步曲之四,结束绘制
//		5.渲染五步曲之五,翻转显示
//*****************************************************************************************

void Direct3D_Render(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之一】:清屏操作
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
	//--------------------------------------------------------------------------------------

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之四】:绘制图形
	//--------------------------------------------------------------------------------------
	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE,D3DSHADE_GOURAUD);  //这句代码可省略,因为高洛德着色模式胃D3D默认的着色模式

	g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) );//把包含的几何体信息的顶点缓存和渲染流水线相关联
	g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );//指定我们使用的灵活顶点格式的宏名称
	g_pd3dDevice->SetIndices(g_pIndexBuffer);//设置索引缓存
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 17, 0, 16);//利用索引缓存配合顶点缓存绘制图形


	//在窗口右上角处,显示每秒帧数
	int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );
	g_pFont->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_XRGB(255,239,136));

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之五】:显示翻转
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}


//*****************************************************************************************
// Name:Get_FPS()函数
// Desc: 用于计算帧速率
//*****************************************************************************************
float Get_FPS()
{

	//定义四个静态变量
	static float  fps = 0; //我们需要计算的FPS值
	static int    frameCount = 0;//帧数
	static float  currentTime =0.0f;//当前时间
	static float  lastTime = 0.0f;//持续时间

	frameCount++;//每调用一次Get_FPS()函数,帧数自增1
	currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间

	//如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零
	if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟
	{
		fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值
		lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间
		frameCount    = 0;//将本次帧数frameCount值清零
	}

	return fps;
}


//*****************************************************************************************
// Name: Direct3D_CleanUp()
// Desc: 对Direct3D的资源进行清理,释放COM接口对象
//*****************************************************************************************
void Direct3D_CleanUp()
{
	//释放COM接口对象
	SAFE_RELEASE(g_pIndexBuffer)
	SAFE_RELEASE(g_pVertexBuffer)
	SAFE_RELEASE(g_pFont)
	SAFE_RELEASE(g_pd3dDevice)	
}


多次运行这个demo,我们会得到如下万花筒般的绚丽的十六边形,因为构成这个十六边形的每个顶点的颜色都是随机的,配合Direct3D中默认的高洛德(GOURAUD)着色模式,就会得到这样颜色平滑过渡的绚丽的十六边形来。

下面是几张运行的截图:

关于这篇源代码里关键点,浅墨只想主要对四步曲中的第三步,访问顶点缓存进行下讲解。这个demo之中,我们依旧采用的是第二种方式:

事先准备好顶点数据的数组,然后在Lock和Unlock之间用下memcpy函数,进行数组内容的拷贝。

主要是这两个数组中定义的内容,相关代码如下:

//--------------------------------------------------------------------------------------
		// 【顶点缓存、索引缓存绘图四步曲之三】:访问顶点缓存和索引缓存
		//--------------------------------------------------------------------------------------
		//顶点数据的设置,
		CUSTOMVERTEX Vertices[17];
		Vertices[0].x = 400;
		Vertices[0].y = 300;
		Vertices[0].z = 0.5f;
		Vertices[0].rhw = 1.0f;
		Vertices[0].color = D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		for(int i=0; i<16; i++)
		{
			Vertices[i+1].x =  (float)(250*sin(i*3.14159/8.0)) + 400;
			Vertices[i+1].y = -(float)(250*cos(i*3.14159/8.0)) + 300;
			Vertices[i+1].z = 0.5f;
			Vertices[i+1].rhw = 1.0f;
			Vertices[i+1].color =  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256);
		}

		//填充顶点缓冲区
		VOID* pVertices;
		if( FAILED( g_pVertexBuffer->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 ) ) )
			return E_FAIL;
		memcpy( pVertices, Vertices, sizeof(Vertices) );
		g_pVertexBuffer->Unlock();

		//索引数组的设置
		WORD Indices[] ={ 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,9, 0,9,10, 0,10,11 ,0,11,12, 0,12,13 ,0,13,14 ,0,14,15 ,0,15,16, 0, 16,1 };

		// 填充索引数据
		WORD *pIndices = NULL;
		g_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
		memcpy( pIndices, Indices, sizeof(Indices) );
		g_pIndexBuffer->Unlock();


第一个数组Vertices中,我们首先确定了圆心,位于(400,300.0.5)处,然后使用for循环,每次循环中顺时针旋转一个八分之π,确定下一个顶点的位置。并且取横坐标x=(float)(250*sin(i*3.14159/8.0)) + 400,纵坐标y=-(float)(250*cos(i*3.14159/8.0)) + 300。竖坐标z一直为0.5,因为我们这里绘制的依然是2D图形,所以竖坐标z在这里不变。

最后我们讲解一下索引缓存数组的设置。最好结合文章开头的第二个引例来进行立即,一个为八边形,一个为十六边形,基本思路一致。其实非常简单,就是0,1,2为一组,做为第一个三角形。然后顺时针往后,0,2,3为一组,表示第二个三角形。然后就是0,3,4…………到0,15,16,最后是0,16,1,转了一圈,回到起点了。

另外提一点,在渲染五步曲第一步Clear的时候,要加个D3DCLEAR_ZBUFFER选项,用于清除Z缓存,不然可能显示不出图形来。因为我们涉及到了Z坐标,却依然是二维坐标系。(感谢xtgsyjx童鞋提出这个tips~ )。

文章最后,依旧是放出本篇文章配套源代码的下载:

本节笔记配套源代码请点击这里下载:

【浅墨DirectX提高班】配套源代码之五下载

其中图标素材使用的是Dota2中的黑暗游侠的大招“射手天赋“

以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。

浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。

另外,浅墨有幸成为了2012年CSDN年度博客之星候选人之一。
在这里,恳请支持浅墨,喜欢游戏开发系列文章的朋友们去
投浅墨一票,有了大家的支持,浅墨会更用心地写出更优秀的博客文章来与大家分享,把技术分享这种信仰传递下去。

大家的支持就是浅墨继续写下去的动力~~~(有朋友说想给我投票却不好找链接,其实链接浅墨已经给出了- -,点击上面红色的“投浅墨一票”字样即可跳到浅墨的参展页面)

文章最后,依然是【每文一语】栏目,今天是来自七堇年的句子:

也许一个人要走很长的路,经历过生命中无数突如其来的繁华和苍凉才会变得成熟。

下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics