OpenGL 体素引擎运行缓慢。

6
我正在使用C++和OpenGL(类似于Minecraft)制作一个体素引擎,但在我的3GHz ATI X1600上无法获得良好的fps...我已经没有更多的想法了。
当屏幕上有大约12000个立方体时,它的帧率降至不到20fps - 糟糕透了。
到目前为止,我所做的优化包括:视锥剔除、背面剔除(通过OpenGL的glEnable(GL_CULL_FACE)实现)、引擎仅绘制可见面(当然除了被剔除的面)并将它们放在八叉树中。
我尝试过VBO,但我不喜欢它们,而且它们并没有显著提高fps。
Minecraft的引擎怎么能跑得这么快...我在处理10000个立方体时都很吃力,而Minecraft可以轻松地以更高的fps绘制更多的立方体。
有什么想法吗?

可能是Culling techniques for rendering lots of cubes的重复问题。 - Yakov Galka
当你说你尝试过VBO时,是使用一个包含单个立方体的单个VBO,并将其glTranslate()到各个位置,还是将许多立方体打包到一个VBO中? - genpfault
我不相信Minecraft一次实际上可以显示超过几千个方块,而且游戏的大部分可能只有20-30个方块。然而,一个相对现代的显卡应该没有问题处理比12k方块更多的数据。 - Puppy
使用VBO。它们会有所帮助。 - Anubian Noob
我目前使用实例化渲染、VBO和VAO,在50帧的速度下渲染80000多个体素。这确实有很大的区别。每批处理的实例数约为4096个。 - StarShine
显示剩余2条评论
5个回答

6

@genpfault: 我分析了连接性并仅为外部可见表面生成面。 VBO有一个单一的立方体,我使用glTranslate()进行了平移。

我不是OpenGL专家,但据我所知,这样做节省的时间很少,因为您仍然需要将每个立方体发送到显卡。

相反,您应该为所有外部可见表面生成面,将其放入VBO中,并将其发送到显卡,直到几何形状发生变化为止,继续渲染该VBO。这样可以节省您的显卡实际上等待处理器发送几何信息的时间。


4
你应该对代码进行性能分析,找出应用程序中的瓶颈是在CPU还是GPU上。例如,你的剔除/八叉树算法可能很慢,这种情况下它根本不是OpenGL的问题。
我还建议你统计每帧绘制的立方体数量,并在屏幕上显示。这样你就知道你的剔除例程是否按预期工作了。
最后,你没有提到你的立方体是否纹理化。尝试使用较小的纹理或禁用纹理,看看帧速率增加了多少。 gDEBugger是一个很棒的工具,可以帮助你找到OpenGL的瓶颈。

谢谢,我会尝试使用性能分析工具。我的方法非常简单,因为我基本上没有任何三维经验...那么算法呢? - Solenoid

2

我不知道在这里是否可以“顶”一个旧问题,但有几个问题出现在我的脑海中:

如果您的体素是静态的,则可以通过使用八叉树进行视锥体裁剪等操作来加速整个渲染过程。此外,还可以将静态场景编译为八叉树中的潜在可见集。 PVS的主要原则是针对树中的每个节点预先计算哪些其他节点是潜在可见的,并在向量中存储指向它们的指针。在进行渲染时,您首先检查摄像机放置在哪个节点中,然后针对该节点的PVS向量中的所有节点运行视锥体裁剪。(Carmack在Quake引擎中使用了类似的方法,但是使用了二进制空间分割树)

如果您的体素着色方式比较复杂,则还可以执行预深度通道(Pre-Depth-Only-Pass),而无需写入颜色缓冲区,只需填充深度缓冲区。之后,您进行第二次传递:禁用对深度缓冲区的写入,并仅对颜色缓冲区进行渲染,同时检查深度缓冲区。因此,您避免了昂贵的着色器计算,这些计算稍后被更靠近观察者的新片段覆盖。(Carmack在Quake3中使用了这个技术)

另一个肯定会加速事情的方法是使用实例化。您仅将每个体素的位置以及必要时其比例和其他参数存储到纹理缓冲区对象中。在顶点着色器中,您可以读取要生成的体素的位置并创建体素的实例(即通过顶点缓冲区对象提供给着色器的立方体)。因此,您只需要一次发送8个顶点+ 8个法线(3 * sizeof(float) * 8 + 3 * sizeof(float) * 8 +浮点数用于颜色/纹理等...)到VBO中,然后只需在TBO中发送立方体的实例位置(3*sizeof(float)*体素数量)。

也许可以通过将所有3个步骤结合在2个线程间并行化GPU和CPU之间的处理,例如,在CPU线程中,您可以检查八叉树PVS并更新TBO以便在下一帧进行实例化,而GPU线程则同时执行两个传递,同时使用由CPU线程在上一步骤中创建的TBO进行实例化。之后,切换TBO。如果摄像机没有移动,则甚至无需重新进行CPU计算。

您可能还对一种称为k-d-tree的树感兴趣,它比八叉树更通用。

PS:抱歉我的英语不是很清晰...


我已经成功尝试了八叉树,但是Minecraft有一个“块”的概念,而不是八叉树:你基本上在一个平面上,平均来说,在x-y方向上有更多可见的平面而非z方向上,因此一个二维网格需要更少的计算,因为进行二维可见性检查较为简单。不过这只是针对这个游戏而言的一个相当特殊的情况。 - Solenoid

1

有第三方库可以用来使渲染更加高效。例如,C++ PolyVox library 可以以高效的方式获取体积并为您生成网格。它具有内置方法来减少三角形数量,并帮助生成环境光遮蔽等内容。它拥有良好的社区支持,因此在论坛上获取支持应该很容易。


0
你是否使用了一个通用的显示列表来渲染所有的立方体?
对于用户看不到的立方体,你是否跳过了它们的绘制代码调用?

是的,每个面都在显示列表中,每个立方体都保存有要绘制的面和立方体位置的数据(glTranslatef和glCallList)。此外,由于八叉树的存在,当立方体不在视野中时,绘制部分会被跳过。 - Solenoid
我认为应该将立方体(而不是面)放入显示列表中。 - rlods
在这种情况下,我将绘制所有的面,即使是在两个立方体之间(看不见的),这需要更多的资源。 - Solenoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接