8.1 Direct3D的深度测试

关于Direct3D的深度测试

什么是深度测试?可以设想在一个比较复杂的游戏场景中,通常需要绘制多个物体,这些物体之间必然会存在遮挡关系,离观察点较远的物体会因为近处物体的遮挡而不可见或只有部分可见,Direct3D中提供深度测试功能来实现这种效果。
深度测试与Alpha混合有什么关系?其实在现实世界中遮挡也分不同的情况,如果遮挡另一个物体的物体是半透明的,那么此时在场景中观察到的被遮挡物体并非是不可见的,也并非是正常可见的,而是“模糊可见”(请允许让我这样形容,你一定可以理解我要表达的意思)的。然而,深度测试只是提供了“可见”和“不可见”两种测试结果,对于这种半透明遮挡便无能为力,此时便可以用Alpha混合技术产生这种半透明遮挡的效果,有关半透明技术的使用会在下一篇文章中详细记录。
下面用Photoshop软件模拟了一下上述表达的意思,图中黑色mask的透明度依次是100%、80%、0%,很明显中间80%的透明度中的红色三角形不能简单的用“可见”与“不可见”来表达。
这里写图片描述
这里写图片描述

深度测试技术的实现

要理解深度测试,首先需要理解深度缓冲区。深度缓冲区是Direct3D用来,存储绘制到屏幕上的每个像素点的深度信息的一块内存缓冲区。当Direct3D将一个场景渲染到目标表面上时,它使用深度缓冲区来决定各个图形的像素的前后遮挡关系,最终决定那个颜色值会被绘制出来。
这里写图片描述
可以结合上面深度测试的原理图来理解这一过程。Direct3D通过比较当前绘制的像素点和对应深度缓冲区的点的深度值来决定是否绘制当前像素。如果深度测试结果为true,则绘制当前像素,并用当前像素点的深度值来更新深度缓冲区,反之则不予绘制。通常情况下,深度缓冲区对应于屏幕大小的一块二维 区域。对于一个启用了深度缓冲区的场景进行光栅化处理时,渲染表面上的每个点都要进行深度测试。
拿上图中的绘制情景梳理这一过程:在深度测试开始时,深度缓冲区的深度值被设置为该场景可能出现的最大值。渲染表面的颜色值被设置为背景颜色值。首先渲染紫色的三角形,Direct3D首先会判断此三角形像素绘制的位置(x,y)是否出现在渲染表面上,如果出现,测试此像素点的深度值,看它是否小于存储在深度缓冲区中的深度值,因为开始的时候,深度缓冲区被设置为场景中可能的最大值,所以,此处紫色三角形像素的深度值要小于深度缓冲区中原有的深度值,则该深度值被更新到深度缓冲区当中,并将渲染表面上当前点的颜色值替换为紫色;绘制黄色五边形的时候,深度测试进行到两个图形重合的部分,由上面的分析过程可知,黄色的深度值比紫色的深度值更小,所以深度缓冲区会被更新成黄颜色所处的深度,而屏幕上两图形重合的部分也只会渲染成黄色。这样,从我们用户看来,后面紫色的三角形便被前面黄色的五边形“遮挡”了!
注意:在上面的叙述中涉及到两个操作(已经被设置为粗体表示):比较(compare)和更新(update)。这两个操作 在使用深度测试的程序代码中会有所体现。

在程序中使用深度测试

由深度测试技术概念的分析中可以概括出使用深度测试的基本步骤:

(1)创建深度缓冲区
(2)激活深度测试
(3)设置深度测试函数
(4)更新深度缓冲区
(5)清除深度缓冲区

下面简单的分析一下上述每个基本步骤的作用和使用方法。
(1)创建深度缓冲区
若要在Direct3D图形程序中使用深度测试,首先必须在创建Direct3D渲染设备时创建深度缓冲区。还记得在最开始创建Direct3D设备的时候,我们使用了一个用来描述Direct3D设备信息的结构D3DPRESENT_PARAMETERS,这个结构包含了不少成员,这里不再一一列出,如果还不是很清晰的话,可以到之前的文章中看看。
之前的定义是这样的:
这里写图片描述
而此处我们为了创建深度缓冲区需要添加两条语句:
这里写图片描述
这里第一条语句表明了由Direct3D创建并管理一个深度缓冲区;第二条语句说明深度缓冲区中每一个像素的深度值由16位二进制表示。使用这两个语句来初始化d3dpp结构,并创建的Direct3D设备便具有了深度缓冲区结构。
(2)激活深度测试
通过“创建深度缓冲区”的步骤,我们的Direct3D设备拥有了进行深度测试的数据结构,但是我们还需要将其激活,类似于设置其他渲染状态,用我们的老朋友SetRenderState()函数来激活深度测试:
这里写图片描述
这里第一个参数设置为D3DRS_ZENABLE,第二个蚕食设置为TRUE,激活深度测试。
注意:默认是激活状态,此语句可以省略,但是为了能让大家明白设置整个深度测试的完整过程,此处并未省略。
(3)设置深度测试函数
还记得之前我让你格外注意的两个操作吗?其中一个便是“比较”操作。这个操作的存在相当自然,设想一下,在绘制那个黄色五边形的时候,我们需要进行深度测试,而具体的操作就是把黄色像素点和之前已经存在过的紫色像素点的深度做一个比较,相应的会返回一个BOOL类型的比较结果,如果是TRUE我们就会进行下一步的“更新”操作……等等。那么什么时候应该返回TRUE呢?因为我们已知的比较操作就有很多:等于、小于、大于、大于等于、小于等于……。你可能会想,因为前面的物体会遮挡后面的物体,那么当欲渲染的像素深度小于深度缓冲区中同一位置已经存在的像素深度的时候,我们应该返回TRUE从而让Direct3D来更新它。没错!这说明你并没有被这些概念冲昏头脑,目的仍然很明确:就是为了营造游戏世界中的遮挡效果!但是,游戏世界中也会存在其他一些情景,如果角色可以透视呢?所以,仅仅定义小于操作便会捉襟见肘,为此Direct3D提供了设置深度测试过程中的比较函数的方法。
接下来仍然调用LPDIRECT3DDEVICE9::SetRenderState()函数设置深度测试的函数,第一个参数设置为D3DRS_ZFUNC,第二个参数设置为想要设置的深度测试函数,它属于D3DCMPFUNC枚举类型,定义如下:
这里写图片描述
D3DCMPFUNC枚举类的详细说明如下:
这里写图片描述
通常情况下,深度测试函数设置为D3DCMP_LESS,表示当前测试点深度值小于深度缓冲区中相应的值时,通过测试并绘制相关像素,这样没有被遮挡的物体才显示,而被遮挡的物体就不显示。此处我们使用下面的代码语句:
这里写图片描述
(4)更新深度缓冲区
上面强调的第二个操作便是“更新”操作,即:设置了深度测试函数后,还需要测试深度测试成功时对缓冲区如何操作,是保持原来的深度值,还是用当前像素深度值更新对应的数值。在程序中我们用如下代码来控制:
这里写图片描述
表示如果通过深度测试,则用当前像素的深度值更新深度缓冲区中对应的数值。这是最常用的设置,也是默认设置。
(5)清除深度缓冲区
清除深度缓冲区与之前的清除后台颜色缓冲区在同一个函数中实现(IDirect3DDevice9::Clear),表示清除当前的后台缓冲区,从而为下一场景的渲染做准备。调用方式如下:
这里写图片描述
这里,第三个参数增加了D3DCLEAR_ZBUFFER标志位,表示在每次清除后台缓冲区时,需要将深度缓冲区一并清除,并且将清空后的深度缓冲区中每个像素的深度值设置为第五个参数所表示的大小;第五个参数的取值范围是[0,1],这里设置为1,表示场景中可能的最大深度。
到这里,关于Direct3D中深度测试的基本概念及使用方法都介绍完了,下面我们用一个实例来加深这一过程的理解。好记性不如烂代码,难道不是吗?

一个实例

实例思路:在场景中创建一个灰色矩形作为遮挡板,一个红色的茶壶作为被遮挡物体。其中,茶壶的像素深度要大于遮挡板像素的深度(即正常情况下遮挡板会挡住茶壶)。
在关闭深度测试的情况下,遮挡板并没有遮住茶壶(因为茶壶是后渲染的)。通过按下键盘上的‘W’和‘S’键可以打开或关闭深度测试,从而可以感受到深度测试的作用效果。
运行效果:
打开与关闭深度测试:
这里写图片描述
二者在世界坐标系中的相对位置:
这里写图片描述
注意事项: 下面只是给出了程序中关键地方的代码,完整程序可以在文章末下载。
关键代码:
观察矩阵与投影矩阵的设置:

//创建并设置观察矩阵
    D3DXVECTOR3 vEyePt(0.0f, 10.0f, -15.0f);        //摄像机位置向量
    D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);    //摄像机朝向向量
    D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);       //指定摄像机上方的朝向向量
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
    g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);

    //创建并设置投影矩阵
    D3DXMATRIXA16 matProj;
    D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0, 1.0f, 1.0f, 1000.0f);
    g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);

渲染状态的设置:

//启用深度测试
    g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);      //激活深度测试,默认是激活的

    //设置深度测试
    g_pd3dDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESS); //设置深度测试函数

    g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE); //通过测试则更新深度缓冲区,默认是更新的

    g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0xffffffff);    //设置环境光

场景中图形的创建函数:

/*
* 创建场景图形,即在三维世界中要渲染的模型。包括一个茶壶模型
* 和一个由顶点缓冲区创建的矩形(遮挡板)模型
*/
HRESULT InitGriphics() {
    //遮挡板顶点数据
    CUSTOMVERTEX g_Vertices[] = {
        { -1.0f, -1.0f, -5.0f, 0xff808080 },
        { -1.0f, 1.0f, -5.0f, 0xf808080 },
        { 1.0f, -1.0f, -5.0f, 0xf808080 },
        { 1.0f, 1.0f, -5.0f, 0xf808080 }
    };
    //创建遮挡板顶点缓冲区
    if (FAILED(g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0,
        D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pShutter, NULL))) {
        return E_FAIL;
    }
    //填充遮挡板的定点缓冲区
    VOID* pVertices;
    if (FAILED(g_pShutter->Lock(0, sizeof(g_Vertices), (void**)&pVertices, 0))) {
        return E_FAIL;
    }
    memcpy(pVertices, g_Vertices, sizeof(g_Vertices));
    g_pShutter->Unlock();

    D3DXCreateTeapot(g_pd3dDevice, &Teapot, 0);

    return S_OK;
}

场景渲染代码:

//开始渲染
    if (SUCCEEDED(g_pd3dDevice->BeginScene())) {
        //渲染遮挡板
        g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
        g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matShutter);
        g_pd3dDevice->SetStreamSource(0, g_pShutter, 0, sizeof(CUSTOMVERTEX));
        g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
        g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

        ////渲染茶壶
        g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
        g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matTea);
        g_pd3dDevice->SetMaterial(&TeapotMtrl);
        g_pd3dDevice->SetTexture(0, 0);
        Teapot->DrawSubset(0);

        g_pd3dDevice->EndScene();
    }
    g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  //提交到前台缓冲区

值得注意的是,茶壶所在的位置是世界坐标中的原点处;而且渲染顺序是先渲染的遮挡板后渲染的茶壶;默认情况下深度测试是激活的。

下载源代码

链接:https://pan.baidu.com/s/1k8GurbSdYGo9aTlyWRc0Xg 密码:8cdp

过关Direct3D中的深度测试就介绍到这里,下一篇来探讨Alpha混合技术的使用。

猜你喜欢

转载自blog.csdn.net/weixin_37818081/article/details/80229359
8.1
今日推荐