分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/zhmxy555/article/details/8607864
作者:毛星云(浅墨) 邮箱: happylifemxy@163.com
在游戏三维场景中,想创造出唯美而具画面感和真实感的画面,常常需要绘制大量的物体。而这些物体之间通常都存在着遮挡的关系,离观察点较远的物体,会因为近处物体的遮挡而不可见,或者只有部分可见。在Direct3D中,微软为我们提供了,深度缓存(或称Z缓存),配套着深度测试技术,来专门实现这种效果。下图是本篇文章配套程序的一个截图,可以发现,利用了深度测试,才能让战士与这个矩形墙面水乳交融,表达出这个战士似乎是从墙面中穿越而出的真实视觉效果。
所以,想要绘制出真实的游戏三维场景,深度测试便是必须之物。
本篇文章里,我们就来探究一下深度测试的方方面面。
一、形象化理解深度测试
首先奉上浅墨一直对深度测试的形象化理解。
把深度测试看做在一口井的井口处向井中观望。把所有物体都赋予一个深度值,放到井中来显示。深度越深的物体,离井口就越远。深度越浅的物体,离井口就越近。井表面的深度值为0。离井口近而深度浅的物体,可能会把离井口远的物体遮挡住。最终显示在屏幕上的开启深度测试后的画面,就如在井口处向井中观望里面物体显示出的遮挡与层次的效果一样。
(每次想到这里,浅墨就会联想起美剧《行尸走肉》第二季里面的农场里的那口井,可惜井中掉下去一个行尸,把井水污染了。。。。。)
当然,离井口的深度就是每个物体在世界坐标系中的矩阵的Z坐标值了。
然后,我们看一下深度测试的具体概念。
二、深度测试相关概念讲解
而想要理解深度测试,首先需要理解深度缓冲区。
深度缓冲区,也常常称为Z缓存,是Direct3D中用来存储绘制到屏幕上的每个像素点的深度信息的一块内存缓冲区,是一个只含有特定像素深度信息而不含图像数据的表面,深度缓存为最终绘制的图像中的每一个像素都保留了一个深度值,如果我们绘制屏幕的分辨率为800×600像素的话,那么深度缓存的大小也为800×600。
当Direct3D讲一个场景渲染来目标表面上时,它使用深度缓冲区来决定光栅化后各个多边形的像素前后的遮挡关系,最终决定哪个颜色值会被绘制出来。
所以可以这样理解,Direct3D通过比较当前绘制的像素点的深度和对应深度缓冲区的点的深度值来决定是否绘制当前像素。如果深度测试的结果为TRUE,就会绘制当前像素,并用当前像素点的深度值来更新一下深度缓冲区,反之,则不予绘制。而在通常情况下,深度缓冲区对应于屏幕大小的一块二维区域。
比如,我们在三维场景中同时绘制了一把长剑和一个盾牌两个立体模型。如果长剑的深度值为30,盾牌的深度值为60( 如取景变换中摄像机的位置矩阵在Z轴上为负值D3DXVECTOR3 vEye(0.0f, 1.0, -100.0f),长剑模型的世界矩阵为(0.0f,0.0f,30.0f),盾牌的世界矩阵为(0.0f,0.0f,60.0f)),那么就是表示长剑的盾牌的前面。Direct3D会首先绘制长剑和盾牌中的其一,而当绘制剩下的那个3D模型的时候,Direct3D会将当前3D模型位于同一位置的像素与已经绘制的像素(如果两者在该位置都有像素的话)进行测试,若当前像素比原来的像素更近(即深度值更小),那么将用当前像素来更新掉原来的像素(前提是),否则不予更新。
当Direct3D将一个场景渲染到目标表面的时候,它使用深度缓冲区决定光栅化后每个多边形的像素前后的遮挡关系,最终决定哪个颜色值被绘制出来,对于一个启用了深度缓冲区的场景进行光栅化操作时,表面上的每个值都要进行深度测试。
三、深度测试使用四步曲
在Direct3D中,使用深度测试有一个四步曲,但是这仅仅是一个按部就班的四步曲,后三步的顺序可以随意,只要在渲染前就可以了。而且由于Direct3D默认开启深度测试,甚至后面三步根本就不需要,就可以在程序中畅通无阻地使用深度测试。
但是,我们本着学知识的态度,依然是要介绍并掌握一下,这样在实现某些特殊渲染效果的时候才有“招”可用。就像现在有了游戏引擎,想要快速做一款游戏的话,完全可以去直接用现成的游戏引擎。但是那毕竟是别人的东西,是别人写出来的东西。如果本着学知识的态度的话,我们应该了解引擎的底层实现,知其然并知其所以然。这样,日积月累,才会显得功力比那些做表层学问的人们来得深厚,才能成为真正的技术大牛,实现起游戏画面优化来游刃有余,甚至有能力写出属于自己的游戏引擎。
好像一下扯远了,囧,还是拉回来吧。深度测试使用四步曲如下:
1.创建深度缓冲区
2.开启深度测试
3.设置深度测试函数
4.更新深度缓冲区
下面我们分别来详细讲解。
1.创建深度缓冲区
关于深度缓冲区的创建,因为是在Direct3D初始化时顺手创建的,所以我们在之前讲解Direct3D初始化时,在《【Visual C++】游戏开发笔记三十三 浅墨DirectX提高班之二 化腐朽为神奇:DirectX初始化四步曲》一文"Direct3D初始化四步曲之三:填内容"中就有提到。
回忆之前的Direct3D初始化四步曲知识,四步曲之三,其实从头到尾其实就是在填充一个D3DPRESENT_PARAMETERS结构体,下面我们先贴出这个结构体的原型:
typedef struct D3DPRESENT_PARAMETERS { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval;} D3DPRESENT_PARAMETERS,*LPD3DPRESENT_PARAMETERS;
其中的第十和第十一个参数是我们今天深度测试相关的。
◆第十个参数,BOOL 类型的EnableAutoDepthStencil,表示Direct3D是否为应用程序自动管理深度缓存,这个成员为TRUE的话,表示需要自动管理深度缓存,这时候就需要对下一个成员AutoDepthStencilFormat进行相关像素格式的设置。
◆第十一个参数,D3DFORMAT类型的AutoDepthStencilFormat,上面刚介绍过,如果我们把EnableAutoDepthStencil成员设为TRUE的话,在这里就需要指定AutoDepthStencilFormat的深度缓冲的像素格式。具体格式可以在结构体D3DFORMAT中进行选取。我们列举一些可以选取的值:
D3DFMT_D16 深度缓存用16位存储每个像素的深度值
D3DFMT_D24X8 深度缓存用24位存储每个像素的深度值
D3DFMT_D32深度缓存用32位存储每个像素的深度值
想要在之后绘制时使用深度测试的话,在这里就要把第十个参数EnableAutoDepthStencil设为true,表示让Direct3D创建并自行管理一个深度缓冲区。而第十一个参数AutoDepthStencilFormat用于设置一下深度缓存的格式,16位,24位,32位等等格式任选。
2.开启深度测试
这一步的主角依旧是我们的老朋友SetRenderState,我们在之前已经用过它和介绍过他无数遍了。因为这个函数的一个参数可以取各式各样的渲染状态和类型,所以注定了它是一个多面手。下面将要介绍的深度使用四步曲的后三步,全都拜倒在了SetRenderState的“石榴裙”之下。
先看一下我们正在讲解的第二步,开启深度测试。自然就是给SetRenderState两个参数取不同的值就可以了。将第一个参数设为D3DRS_ZENABLE,表示第二个参数将对深度测试的开启或者关闭进行设置,第二个参数设为TRUE或者FALSE,表示开启或者关闭深度测试。
我们这步的目的当然就是要开启深度测试,也就是这样写:
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE,true); //开启深度测试
当然,要关闭深度测试的话,把第二个参数取false就可以了:
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE,false); //关闭深度测试
3.设置深度测试函数
还是那个神通广大的SetRenderState函数。对应于这一步的任务,我们把它的第一个参数设为D3DRS_ZFUNC,第二个参数设为想要进行使用的深度测试函数,在D3DCMPFUNC枚举类型中取值,这个D3DCMPFUNC枚举类型我们可以在MSDN中查到定义如下:
typedef enum D3DCMPFUNC { D3DCMP_NEVER = 1, D3DCMP_LESS = 2, D3DCMP_EQUAL = 3, D3DCMP_LESSEQUAL = 4, D3DCMP_GREATER = 5, D3DCMP_NOTEQUAL = 6, D3DCMP_GREATEREQUAL = 7, D3DCMP_ALWAYS = 8, D3DCMP_FORCE_DWORD = 0x7fffffff} D3DCMPFUNC, *LPD3DCMPFUNC;
下面我们通过一个表格,对这些枚举类型中的成员进行讲解说明:
枚举类型值
精析
D3DCMP_NEVER
深度测试函数总是返回FALSE
D3DCMP_LESS
测试点深度值小于深度缓冲区中相应值时,返回TRUE,为默认值
D3DCMP_QUAL
测试点深度值等于深度缓冲区中相应值时,返回TRUE
D3DCMP_LESSEQUAL
测试点深度值大于等于深度缓冲区中相应值时,返回TRUE
D3DCMP_GREATER
测试点深度值大于深度缓冲区中相应值时,返回TRUE
D3DCMP_NOTEQUAL
测试点深度值不等于深度缓冲区中相应值时,返回TRUE
D3DCMP_GREATEREQUAL
测试点深度值大于等于深度缓冲区中相应值时,返回TRUE
D3DCMP_ALWAYS
深度测试函数总是返回TRUE
D3DCMP_FORCE_DWORD
这个枚举值一般不用,保证将D3DCMPFUNC枚举类型编译为32位
一般我们都将深度测试函数设为D3DCMP_LESS,表示当测试点深度值小于深度缓冲区中相应值时,通过测试并绘制相关像素,这样没有被遮挡的物体才显示,被遮挡住的物体是不显示的。
这步里面也就是一句SetRenderState代码:
g_pd3dDevice->SetRenderState(D3DRS_ZFUNC,D3DCMP_LESS);
4.更新深度缓冲区
配合第三步设置的深度测试函数,还需要设置深度测试成功时对深度缓冲区如何操作,是保持原来的深度值“按兵不动”呢,还是用当前像素的深度值来更新对应的数值“与时俱进”。
还是那个神通广大的SetRenderState函数。对应于这一步的任务,我们把它的第一个参数设为D3DRS_ZWRITEENABLE,表示在第二个参数里面将对深度缓冲区更改与否做出选择。第二个参数设为TURE的话,表示深度测试成功之后,用当前像素的深度值更新深度缓冲区对应的数值。第二个参数设为TURE是设置更新深度缓冲区时最常用的设置,同时也是默认值。反之,第二个参数设为FALSE,则表示尽管深度测试成功,还是不更新深度缓冲区对应的数值。
代码书写方面,就是这样写:
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE,true); //深度测试成功后,更新深度缓存
四、文章配套程序源代码赏析
本篇文章的配套源代码依旧是包含四个文件,主要用于公共辅助宏定义的D3DUtil.h,用于封装了DirectInput输入控制API的DirectInputClass.h和DirectInputClass.cpp最后才是核心代码main.cpp。
其实D3DUtil.h,DirectInputClass.h以及DirectInputClass.cpp在上篇文章的配套demo的基础上并没有做任何修改,我们只是修改了main.cpp中的代码而已。鉴于这个三个文件在之前的基础上无任何修改且在前面的文章中已经贴出过多次,这次就不再费篇幅贴出了,我们只贴出核心代码main.cpp即可:
//*****************************************************************************************////【Visual C++】游戏开发笔记系列配套源码四十五 浅墨DirectX教程十三 深度测试与Z缓存专场// VS2010版// 2013年 2月24日 Create by 浅墨 //图标及图片素材: 《仙剑奇侠传五前传》 龙溟////***************************************************************************************** //*****************************************************************************************// Desc: 宏定义部分 //*****************************************************************************************#define SCREEN_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度#define SCREEN_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度#define WINDOW_TITLE _T("【Visual C++】游戏开发笔记系列配套源码四十五 浅墨DirectX教程十三 深度测试与Z缓存专场") //为窗口标题定义的宏//*****************************************************************************************// Desc: 头文件定义部分 //***************************************************************************************** #include <d3d9.h>#inclu