我正在写一个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建议的范围优化),但它仍然运行得太慢了。
谢谢大家的意见。