第19章 横看成岭侧成峰——深度测试与Z缓存

来源:互联网 发布:数据分析平台 编辑:程序博客网 时间:2024/04/24 20:07
章节导读
在游戏三维场景中,想创造出唯美又具真实感的画面,常常需要绘制大量的物体。而这些物体之间通常都存在着遮挡的关系,离观察点较远的物体,会因为近处物体的遮挡而不可见,或者只有部分可见。在Direct3D 中,微软为我们提供了深度缓存(或称Z 缓存),配套着深度测试技术,来专门实现这种效果。下图是本篇文章配套程序的一个截图,可以发现,利用了深度测试,才能让战士与这个矩形墙面水乳交融,表达出这个战士似乎是从墙面中穿越而出的真实视觉效果。
所以,想要绘制出真实的游戏三维场景,深度测试便是必须之物。

19.1 形象化理解深度测试

首先来看一下对深度测试的形象化理解。
把深度测试看做从一口井的井口向井中观望。把所有物体都赋予一个深度值,放到井中来显示。深度越深的物体,离井口就越远。深度越浅的物体,离井口就越近。井口表面的深度值为0 。离井口近而深度浅的物体,可能会把离井口远的物体遮挡住。最终显示在屏幕上的开启深度测试后的画面,就如在井口处向井中观望里面物体显示出的遮挡与层次的效果一样。
当然,离井口的深度就是每个物体在世界坐标系中矩阵的Z 坐标值了。
接下来,我们看一下深度测试的具体概念。

19.2 深度测试相关概念讲解

而想要理解深度测试,首先需要理解深度缓冲区。
深度缓冲区,也常常称为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 将一个场景渲染到目标表面的时候,它使用深度缓冲区决定光栅化后每个多边形的像素前后的遮挡关系,最终决定哪个颜色值被绘制出来, 对于一个启用了深度缓冲区的场景进行光栅化操作时,表面上的每个值都要进行深度测试。

19.3 深度测试使用四步曲

在Direct3D 中,使用深度测试有一个四步曲,但是这仅仅是一个按部就班的四步曲, 后三步的顺序可以随意,只要在渲染前就可以了。而且由于Direct3D 默认开启深度测试,甚至不需要后面三步,也可以在程序中畅通无阻地使用深度测试。
但是, 我们本着学习的态度, 依然是要介绍一下这个知识点, 这样在实现某些特殊渲染效果的时候才有“招”可用。就像现在有了游戏引擎, 想要快速做一款游戏的话, 完全可以去直接用现成的游戏引擎。但是那毕竟是别人写出来的东西。如果本着学知识的态度的话, 我们应该了解引擎的底层实现, 知其然并知其所以然。这样, 日积月累, 才会显得功力比那些做表层学问的人们来得深厚,才能成为真正的技术大牛, 实现起游戏画面优化来游刃有余,甚至有能力写出属于自己的游戏引擎。

深度测试使用四步曲如下:

  • 创建深度缓冲区。
  • 开启深度测试。
  • 设直深度测试函数。 
  •  更新深度缓冲区。

下面我们分别来详细讲解。

1 . 四步曲之一: 创建深度缓冲区
关于深度缓冲区的创建, 因为是在Direct3D 初始化时顺手创建的,所以我们在之前讲解Direct3D 初始化时,在11.4.4 节就有提到。
回忆之前的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 的话,在这里就需要指定AutoDeptbStencilFormat 的深度缓冲的像素格式。具体格式可以在结构体D3DFORMAT 中进行选取。我们列举一些可以选取的值:
D3DFMT_D16        深度缓存用16位存储每个像素的深度值D3DFMT_D24X8      深度缓存用24位存储每个像素的深度值D3DFMT_D32        深度缓存用32位存储每个像素的深度值
想要在之后绘制时使用深度测试的话,这里需要把第十个参数EnableAutoDeptbStencil 设为true , 表示让Direct3D 创建并自行管理一个深度缓冲区。而第十一个参数AutoDeptbStenciIFormat用于设置深度缓存的格式, 16 位、24 位、32 位等等格式任选。

2. 四步曲之二:开启深度测试
这一步的主角依旧是老朋友SetRenderState , 之前我们己经用过和介绍过无数遍了。因为这个函数的一个参数可以取各式各样的渲染状态和类型,所以注定了它是一个多面手。下面将要介绍的深度使用四步曲的后三步。
先看一下我们正在讲解的第二步,开启深度测试。自然就是给SetRenderState 两个参数取不同的值就可以了。将第一个参数设为D3DRS_ZENABLE , 表示第二个参数将对深度测试的开启或者关闭进行设置,第二个参数设为TRUE 或者FALSE ,表示开启或者关闭深度测试。
这一步的目的是开启深度测试,也就是这样写:

g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, true);   //开启深度测试
当然,要关闭深度测试的话,把第二个参数取false 就可以了:

g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);  //关闭深度测试
3. 四步曲之三:设置深度测试函数
还是那个神通广大的SetRenderState 函数。对应于这一步的任务,我们把它的第一个参数设为D3DRS_ZFUNC , 第二个参数设为想要进行使用的深度测试函数,在D3DCOMPFUNC 枚举类型中取值, 这个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_LESS ,表示当测试点深度值小于深度缓冲区中相应值时,通过测试并绘制相关像素,这样没有被遮挡的物体才显示,被遮挡住的物体是不显示的。
这一步里面也就是一句SetRenderState 代码:

g_pd3dDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESS);   //将深度测试函数设为D3DCMP_LESS
4 . 四步曲之四:更新深度缓冲区
配合第三步设置的深度测试函数,还需要设置深度测试成功时对深度缓冲区如何操作,是保持原来的深度值“按兵不动”呢,还是用当前像素的深度值来更新对应的数值“与时俱进”。
还是那个神通广大的SetRenderState 函数。对应于这一步的任务,我们把它的第一个参数设为D3DRS_ZWRITEENABLE ,表示在第二个参数里面将对深度缓冲区更改与否做出选择。第二个参数设为TURE 的话,表示深度测试成功之后,用当前像素的深度值更新深度缓冲区对应的数值。第二个参数设为TURE 是设置更新深度缓冲区时最常取的值,同时也是默认值。反之,第二个参数设为FALSE ,则表示尽管深度测试成功,还是不更新深度缓冲区对应的数值。
代码是这样写的:

g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);     //深度测试成功后,更新深度缓存

19.4 示例程序D3Ddemo14

示例程序的工程大体上和D3Ddemo12 差不多,依旧是四个文件。
我们在这个程序中绘制了两个物体, 一个是章节开头中的那个帅气的战士,另外-个是一面巍然不动的褐色的矩形墙,本来还可以把这面墙弄成真正的贴过图后的砖块墙的,但是那样未免会增加一些代码量,增大大家理解的难度, 于是就用了这个D3DXCreateBox 函数来图个方便, 快捷绘制一面单色的墙。
有变动的地方是Objects_lnit()函数中载入X 文件的名称变了一下, 然后是用如下的代码创建了一个褐色的极薄的屏障:
//用D3DXCreateBox来创建一个极薄的屏障D3DXCreateBox(g_pd3dDevice, 30.0f, 30.0f, 0.5f, &g_pMeshWall, NULL);g_MaterialsWall.Ambient  = D3DXCOLOR(0.8f, 0.2f, 0.1f, 1.0f);g_MaterialsWall.Diffuse  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);g_MaterialsWall.Specular = D3DXCOLOR(0.1f, 0.1f, 0.1f, 1.0f);
然后Direct3D_Update() 函数中加入了对深度测试开与关进行控制的代码:

// 开启或者关闭深度测试if (g_pDInput->IsKeyDown(DIK_1))  //按下1键,开启深度测试g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, true);if (g_pDInput->IsKeyDown(DIK_2))  //按下2键,关闭深度测试g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
我们可以用键盘上的数字键1 和2 在开启深度测试和关闭深度测试之间切换。

战士的初始世界矩阵Z 坐标为0.0f, 单色墙的世界矩阵Z 坐标为 -50.0f。所以开启深度测试后,战士在我们不动键盘鼠标让其移动的情况下,会被单色墙遮挡住。我们滑动鼠标上的滚轮,让战士的Z 坐标小于 -50.0f 之后, 会发现战士完全位于单色墙之前了,不再被其遮挡。
如果我们按下键盘上的2 键,关闭深度测试的话,会发现单色墙一直拦在战士的身前,战士本来披于身后被面部遮挡住的一头马尾金发辫, 竟然可以从正面看到, 非常地不科学。
接下来我们来看看一些开启/关闭深度测试的对比图。

    
                            开启了深度测试的状态                                                             关闭了深度测试的状态

19.5 章节小憩 

这是非常短小精悍的一章, 我们讨论了深度缓存和深度测试的方方面面。
请大家记住, 在Direct3D中,深度测试默认是打开状态的,所以很多时候我们不需要专门去设置。










阅读全文
0 0