关于优化z缓冲区实现的建议



我正在写一个3D图形库作为我的一个项目的一部分,我在这一点上,一切都工作,但不够好。

特别是,我最头疼的是我的像素填充率非常慢——我甚至不能在我的目标机器上绘制一个跨越800x600窗口一半的三角形时管理30 FPS(这是一台旧电脑,但它应该能够管理这个…)

我在我的可执行文件上运行gprof,最后得到以下有趣的行:

  %   cumulative   self              self     total           
time   seconds   seconds    calls  ms/call  ms/call  name    
43.51      9.50     9.50                             vSwap
34.86     17.11     7.61   179944     0.04     0.04  grInterpolateHLine
13.99     20.17     3.06                             grClearDepthBuffer
<snip>
0.76      21.78     0.17      624     0.27    12.46  grScanlineFill

函数vSwap是我的双缓冲区交换函数,它也执行vsyching,所以对我来说,测试程序将花费大量时间在那里等待是有意义的。grScanlineFill是我的三角形绘制函数,它创建一个边缘列表,然后调用grInterpolateHLine来实际填充三角形。

我的引擎目前正在使用z缓冲区来执行隐藏表面移除。如果我们不考虑(假定的)vsync开销,那么结果是测试程序花费了大约85%的执行时间来清除深度缓冲区,或者根据深度缓冲区中的值写入像素。我的深度缓冲区清除函数本身很简单:将浮点数的最大值复制到每个元素中。函数grInterpolateHLine为:

void grInterpolateHLine(int x1, int x2, int y, float z, float zstep, int colour) {
    for(; x1 <= x2; x1 ++, z += zstep) {
        if(z < grDepthBuffer[x1 + y*VIDEO_WIDTH]) {
            vSetPixel(x1, y, colour);
            grDepthBuffer[x1 + y*VIDEO_WIDTH] = z;
        }
    }
}

我真的不知道如何改进,特别是考虑到vSetPixel是一个宏。

我所有的优化想法都被精简成一个:

    使用整数/定点深度缓冲区

我对整数/定点深度缓冲区的问题是插值可能非常烦人,而且我实际上还没有一个定点数字库。还有什么想法吗?如有任何建议,我将不胜感激。

你应该看看Quake之类的源代码——想想15年前它在Pentium上能实现什么。它的z缓冲区实现使用跨度而不是每像素(或片段)深度。否则,您可以查看Mesa中的光栅化代码。

如果没有看到其余的代码,很难真正知道可以做哪些更高阶的优化。不过,我有几个小小的观察。

在grInterpolateHLine中不需要多次计算x1 + y * VIDEO_WIDTH。例如:

void grInterpolateHLine(int x1, int x2, int y, float z, float zstep, int colour) {
    int offset = x1 + (y * VIDEO_WIDTH);
    for(; x1 <= x2; x1 ++, z += zstep, offset++) {
        if(z < grDepthBuffer[offset]) {
            vSetPixel(x1, y, colour);
            grDepthBuffer[offset] = z;
        }
    }
}

同样地,我猜你的vSetPixel做了类似的计算,所以你应该能够在那里使用相同的偏移量,然后你只需要在每个循环迭代中增加偏移量而不是x1。很有可能这可以扩展回调用grInterpolateHLine的函数,然后您只需要对每个三角形执行一次乘法。

你还可以用深度缓冲区做一些其他的事情。大多数情况下,如果该线的第一个像素没有通过深度测试,那么该线的其余部分将具有相同的结果。因此,在第一次测试之后,您可以编写一个更有效的汇编块来一次性测试整条线,然后如果它通过了,您可以使用更有效的块内存设置器来块设置像素和深度值,而不是一次设置一个。你只需要测试/设置每像素,如果线只是部分遮挡。

还有,不确定你说的旧电脑是什么意思,但如果你的目标电脑是多核的,那么你可以把它分解成多个核。您也可以对缓冲区清除函数这样做。

我最终通过用Painter's Algorithm替换z缓冲区来解决这个问题。我使用SSE编写了一个z缓冲区实现,它创建了一个包含要绘制的像素的位掩码(加上Gerald建议的范围优化),但它仍然运行得太慢了。

谢谢大家的意见。

最新更新