我正在写一个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缓冲区来执行隐藏表面移除。如果我们对(推测的)vsynch开销进行折扣,那么事实证明测试程序花费了85%的执行时间来清除深度缓冲区,或者根据深度缓冲区中的值写入像素。我的深度缓冲清除功能本身就是简单:将float的最大值复制到每个元素中。函数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
是一个宏。
我对优化的全部想法已被削减到恰好一个:
我对整数/定点深度缓冲区的问题是插值可能非常烦人,我实际上还没有一个定点数字库。有没有进一步的想法?任何建议都会非常感激。
答案 0 :(得分:3)
你应该看看像Quake这样的源代码 - 考虑一下它在15年前在Pentium上可以实现的目标。它的z缓冲区实现使用跨度而不是每像素(或片段)深度。否则,您可以查看Mesa中的光栅化代码。
答案 1 :(得分:1)
如果不看其余的代码,很难真正告诉我们可以进行哪些更高阶的优化。不过,我有一些小的观察。
在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的函数,然后你只需要为每个三角形进行一次乘法。
您还可以使用深度缓冲区执行其他一些操作。大多数情况下,如果线的第一个像素失败或通过深度测试,那么线的其余部分将具有相同的结果。因此,在第一次测试之后,你可以编写一个更高效的汇编块来一次性测试整行,然后如果它通过你可以使用更高效的块存储器设置器来阻止设置像素和深度值,而不是在一时间如果该行仅被部分遮挡,您只需要测试/设置每个像素。
此外,不确定您的旧计算机是什么意思,但如果您的目标计算机是多核的,那么您可以在多个核心之间分解它。您也可以为缓冲区清除功能执行此操作。它可以帮助很多。
答案 2 :(得分:0)
我最终通过用Painter算法替换Z缓冲区来解决这个问题。我使用SSE编写了一个Z缓冲区实现,它创建了一个带有绘制像素的位掩码(加上Gerald建议的范围优化),它仍然运行得太慢了。
谢谢大家的意见。