如何有效地在openGL中渲染大量对象?

时间:2016-05-05 19:20:35

标签: opengl

这个问题很广泛,所以我会努力让它尽可能准确。

我编写的程序加载并渲染一个具有多个纹理的模型。由于我使用单一模型,我能够形成所有缓冲区,启用所有顶点属性数组,绑定所有顶点数组,并在负责绘制的循环之前绑定和设置活动纹理。

因此,我在绘图循环的每次迭代中的程序只执行单行glDrawArrays。在大量对象的情况下也可以这样做,或者我需要在绘制循环的每次迭代中形成所有缓冲区,启用attrib数组,绑定和设置活动纹理,设置着色器程序等(这意味着向视频发送大量数据)卡应该慢?)

1 个答案:

答案 0 :(得分:5)

目前还不清楚你究竟在寻找什么 - 以及我们谈论的对象数量。

  

因此,我在绘图循环的每次迭代中的程序只执行单行glDrawArrays。在大量对象的情况下也可以这样做,或者我需要在绘制循环的每次迭代中形成所有缓冲区,启用attrib数组,绑定和设置活动纹理,设置着色器程序等(这意味着向视频发送大量数据)卡应该慢?)

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

您可以使用Vertex Array Objects (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状态意味着性能损失。 标准方法是对每个对象进行分组 着色器程序,纹理等可以绘制多个 没有昂贵的状态开关的对象。

    查看this answer to a related question了解不同状态变化的相对成本。

  2. 增加单次绘制调用绘制的对象数。 在某种程度上,这也意味着方法1,因为你无法切换 绘制调用期间的OpenGL状态。但是,使用现代GL,您可以使用数组纹理并将不同对象的纹理放入单个纹理对象中(您也可以通过使用纹理类来完成没有纹理数组,并且可以将它们组合在一起)。

    这方面的其他非常有趣的功能是

    • instanced rendering(渲染同一个对象的多个实例,每个实例都有一些属性,如模型矩阵)
    • indirect rendering(将几个绘制调用的参数放入另一个缓冲区并使用单个glMultiDrawElementsIndirect()调用执行它。由于源数据来自缓冲区对象,因此您可以将其生成绘图的参数直接在GPU上调用,例如通过计算着色器。
  3. 近年来,已经提出了一些有效渲染许多对象的策略,这些对象在标题Approaching Zero Driver Overhead (AZDO)下已知。