3

我正在编写一个 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是一个宏。

我对优化的全部想法已经缩减为一个:

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

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

4

3 回答 3

3

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

于 2011-11-24T22:50:02.627 回答
1

如果不查看其余代码,很难真正说出可以进行哪些更高阶的优化。不过,我有一些小的观察。

无需在 grInterpolateHLine 中多次计算 x1 + y * VIDEO_WIDTH。IE:

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 的函数,然后您只需对每个三角形进行一次乘法运算。

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

另外,不确定您所说的旧计算机是什么意思,但是如果您的目标计算机是多核的,那么您可以将其分解为多个内核。您也可以对缓冲区清除功能执行此操作。它可以帮助很多。

于 2011-11-24T20:38:50.757 回答
0

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

谢谢大家,您的意见。

于 2011-12-26T20:40:14.257 回答