[3D游戏开发]Early ZBuffer

时间:2022-09-10 23:56:10

     一、最近在优化客户端性能的时候,看到了Early ZBuffer。在VSPS中间GPU会对进行Z-buffer预判机制,对无效像素进行剔除,ATI、NVIDIA都有自己的Z-buffer预判机制。其实Doom3的时候已经开始使用预填充ZBuffer了,因为Doom3的PS需要处理阴影、NormalMap、LightMap及其他贴图处理,PS指令非常多,所以无效像素的剔除对性能影响是很大的,越早将无效像素剔除,显卡便能获得更多的时间对有效像素进行渲染。

 

    二、Early-Z技术介绍(这段摘自http://tech.sina.com.cn/h/2008-06-17/09302262913.shtml

  当代的GPU都会采用Z-buffer去记录哪些像素是可见,而哪些像素是被遮挡而不可见。一个3D Frame最终要转换成为2D图像才能表示在屏幕上面,来自GPU连续的顶点流(vertices)会构建这个frame,从这个顶点流获取相应的2D坐 标去生成多边形。多边形的连续产生会覆盖原来的区域,因而Z-buffer的信息就是告诉ROP,哪些像素是可见哪些是不可见的。提前进行的Early- Z对比可以节省大量资源,因为同一个区域被多个多边形覆盖的次数轻而易举地达到原来的四倍甚至更高。

[3D游戏开发]Early ZBuffer

[3D游戏开发]Early ZBuffer

  目前甚少方法可以利用Z-buffer信息去挑选或者排出被遮挡像素的渲染,Z-Cull就是这样的一个方法。Z-comparision通常 会发生在ROP的后期。问题就产生,意味着pixel要通过完整的ROP管线才能被发现是否可见。一些复杂的包含数千步骤的shader程序,即使是被遮 挡的pixel也全部通过流水线,这显然浪费了GPU的性能。Early-Z移去不可见像素在它们进入流水线之前,这样显然会提高性能,NVIDIA认为 这个操作提升22%附近的性能。

 

    三、具体实现:场景渲染两遍:
    void Render()
    {
        DrawZPass();    
        DrawColorPass();    
    }
    
    // 关闭ColorBuffer写入,以最简单的渲染状态绘制场景
    void DrawZPass()
    {
        // Disable color writes
        pD3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE, 0x00000000);

        // Ensure alpha off
        pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, false );
        pD3DDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false );

        // Ensure z-enabled
        pD3DDevice->SetRenderState( D3DRS_ZENABLE, true );
        pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, true );

        DrawScene();

        pD3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE, 0x0000000F );
    }

    // 正常渲染
    void DrawColorPass()
    {
        pD3DDevice->SetRenderState( D3DRS_DEPTHBIAS, F2DW(-0.001f) );    
        DrawScene()
    }

    1.DrawZPass:应该跳过AlphaBlend、AlphaTest的实体。
   2.DrawColorPass:对于那些预写入ZBuffer的实体,在这个Pass中只需开启ZBufferTest、并且可以关闭ZBufferWrite。
   3.使用了EarlyZBuffer,就不用再排序了。

   4.在第二遍渲染的时候,因为浮点的误差,会有ZFighting现象,所以应允许一定的误差。

    5.Early ZBuffer不一定适用所有场景,比如有大量实体的室外场景,因为DrawZPass毕竟也要绘制所有的实体,如果调用太多DP,性能反而会有所下降。


     四、 另外一个性能优化提示:先画UI;最后绘制天空盒。这也于ZBuffer有关,因为天空盒总是显示在最后,而天空盒总是被前面的实体遮挡了大部分区域。

不过最后绘制天空盒时候,大家会问如何避免被FarPlane裁剪,有一个技巧可以解决,在SkyShader的VS输出投影后的位置时,这样设置:

     Out.position = mul(mvp, vertex).xyww。// 不是Out.position = mul(mvp, vertex);

    这样天空盒投影后的总是映射到FarPlane,这样就完美了,哈哈。这个方法时我在ATI的《Depth In Depth》文档中看到的。UI也是,游戏里的UI区域如果预先写入ZBuffer,也可以避免大量的无效PS处理。


     五、最后希望大家可以仔细看看 《Depth In Depth》,里边有很多优化提示。