如何在OpenGL中高效渲染大量对象?

10
问题比较广泛,我会尽可能精确地回答。我编写了一个程序,加载并渲染了一个带有多个纹理的模型。由于我只使用了一个模型,所以在负责绘制的循环之前,我能够形成所有缓冲区,启用所有顶点属性数组,绑定所有顶点数组,并且只需一次绑定和设置活动纹理。
因此,在每次绘制循环的迭代中,我的程序只执行单个命令-glDrawArrays。在处理多个对象时,是否可以采取同样的方法?或者,我需要在每次绘制循环的迭代中形成所有缓冲区,启用属性数组,绑定和设置活动纹理,设置着色器程序等(这意味着向显卡发送大量数据,可能会很慢)?

1
我猜你想找的是实例化对象的方法,这是一种有效绘制大量对象的方式。请参考 http://www.learnopengl.com/#!Advanced-OpenGL/Instancing 上的教程。 - wangdq
1个回答

14

不清楚您具体需要什么 - 以及我们正在讨论多少个对象。

因此,在每次绘图循环的迭代中,我的程序只执行了单行代码- glDrawArrays。在众多对象的情况下是否可以做到相同的效果,或者我需要在每次绘图循环中形成所有缓冲区、启用属性数组、绑定和设置活动纹理、设置着色器程序等(这意味着发送大量数据到显卡可能会很慢)?

是的,那样会很慢。缓冲区对象允许您以可访问GL的方式存储数据。在理想情况下,GL可以决定直接将该数据存储在VRAM中(尽管您无法完全控制OpenGL)。因此,如果您有静态、不变的网格数据,则上传一次是正确的方法。将许多小对象的数据合并到单个缓冲区中也可能很有用。

您可以使用顶点数组对象(VAOs)来存储顶点属性指针和启用状态,因此在绘制时,您只需绑定VAO并发出绘制调用即可。因此,渲染多个对象的基本方法是

// ... at initinialization
for each object:
    create and upload VBO(s) and index buffers
    create and upload textures
    create and initialize VAO

// at draw time
for each object:
    bind VAO
    bind texture(s)
    set all other object-related OpenGL state
    (like switching programs, setting unforms for
     the model matrix, base colors, ...)
    glDraw*(...)

如果每帧只绘制几百个对象,使用此方法可能不会遇到性能问题。(别误解我的意思,我只是在谈论单个绘制调用的开销,而不是实际对象的渲染成本——如果你的对象有数百万个顶点,或者你正在生成大量片段,你仍然可以通过很少或一个对象来降低性能。在这种情况下,您需要更聪明的策略来高效地渲染对象本身,例如应用细节级别方法)
一般来说,有两种主要方法来提高这种渲染循环的性能:
  1. 减少状态改变的次数。

    切换不同的GL状态会带来性能损失。一种标准方法是按着着色器程序、纹理等将对象分组,这样就可以在不进行昂贵的状态切换的情况下绘制多个对象。

    请查看这个相关问题的答案以了解不同状态改变的相对成本。

  2. 增加单次绘制的对象数量。 从某种程度上讲,这也意味着采用第一种方法,因为你不能在绘制调用期间切换OpenGL状态。然而,在现代GL中,你可以例如使用数组纹理,并将不同对象的纹理放入单个纹理对象中(你也可以通过使用纹理地图集而无需使用纹理数组来完成,这两种方式都可以结合使用)。

    在这方面的其他非常有趣的功能包括

    • 实例渲染(渲染许多相同对象的实例,其中一些属性每个实例都不同,例如模型矩阵)
    • 间接渲染(将多个绘制调用的参数放入另一个缓冲区中,并使用单个glMultiDrawElementsIndirect()调用执行它。由于源数据来自缓冲区对象,因此你可以将其作为直接在GPU上生成绘制调用参数的源数据,例如通过计算着色器。

近年来,已经提出了一些策略来高效渲染许多对象,这些策略以 无驱动开销逼近零 (AZDO) 为标题。


谢谢!非常有用的信息。你刚好读懂了我的想法,甚至回答了我没有提出的问题,因为我想让问题简短 : ) 例如,我一直在思考如何处理OpenGL中的有限纹理,你解释得非常清楚,还有我对VRAM内容的控制水平很感兴趣,你也让这一点变得清晰明了。再次感谢! - qwertyu uytrewq

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