使用单个绘制调用在OpenGL中渲染多个模型

8
我建立了一个2D图形引擎,并为其创建了一个批处理系统,如果我有1000个具有相同纹理的精灵,我可以通过一次OpenGL调用来绘制它们。
这是通过将所有具有相同纹理的精灵的所有顶点放入单个vbo顶点数组中实现的。
我不是“打印这些顶点,打印这些顶点,打印这些顶点”,而是“将所有顶点放在一起,打印”,这很清楚易懂。虽然很容易,但现在我正在尝试在3D中实现相同的效果,但是我遇到了一个大问题。
问题在于,我使用模型视图投影矩阵来放置和渲染我的模型,这是在3D空间中呈现模型的常见方法。对于屏幕上的每个模型,我需要传递MVP矩阵到着色器中,以便我可以使用它将每个顶点转换到正确的位置。
如果我在着色器外执行转换,那么它将由CPU执行,这不是一个好主意,原因是显而易见的。
但问题就在那里。我需要将矩阵传递给着色器,但对于每个模型,矩阵都是不同的。
因此,我不能像处理2D精灵那样处理,因为每次更改着色器统一变量都需要进行绘制。
我希望我已经表达清楚了,也许您有一个我没有想到的好主意,或者您已经遇到了同样的问题。我知道事实上有一个解决方案,因为在像Unity这样的引擎中,您可以使用相同的着色器来渲染多个模型,并且只需进行一次绘制调用即可。
3个回答

9
存在一个与您所寻找的完全相同的功能,它被称为实例化。使用实例化,您可以将n个矩阵(或其他任何所需内容)存储在统一缓冲区中,并调用glDrawElementsInstanced来绘制n个副本。在着色器中,您会得到额外的输入gl_InstanceID,通过它,您可以索引到统一缓冲区,以获取该特定实例所需的矩阵。
您可以在此处阅读更多关于实例化的信息:https://www.opengl.org/wiki/Vertex_Rendering#Instancing

太棒了!我曾经考虑过自己实现类似的东西,但我真的不知道该怎么做,这正是我一直在寻找的。 - Ryno

4
答案取决于每个项目的顶点数据是否相同。如果是相同的,您可以像@orost的答案一样使用实例化,使用glDrawElementsInstanced和顶点着色器中的gl_InstanceID,并且应该首选该方法。
然而,如果每个3D模型需要不同的顶点数据(这种情况经常发生),仍然可以使用单个绘制调用来渲染它们。为此,您将在顶点数据中添加另一个流,使用glVertexAttribPointer(和glEnableVertexAttribArray)。这个额外的流将包含uniform缓冲区内vertex应该使用的矩阵索引 - 所以VBO中的每个网格都应该在额外的流中具有相同的索引。Uniform缓冲区包含与实例设置相同的数据。
请注意,如果需要重新执行批处理 - 例如,批处理中的对象不再需要呈现,则此方法可能需要一些额外的CPU处理。如果频繁需要执行此过程,则应确定批处理项目实际上是否有益。

你认为这合理吗? - Ryno
嗯...等等,我告诉你我的理解,你告诉我哪里错了。这个想法是将所有的顶点放在一个VBO中(每个顶点包含位置、颜色、法线和其他内容)。假设我添加了一个立方体,然后再添加一个巨大的网格(总共1000个顶点)。 我的VBO将有36+1000个顶点。 然后我有一个包含MVP矩阵的统一缓冲区,在这种情况下有两个矩阵。我的理解是要有一个长度为1036的缓冲区,其中前36个指向第一个矩阵,其他指向第二个矩阵。这正确吗? - Ryno
如果这样正确的话,我的想法是:1)我会为每个网格分配一个标识符(因为您说仅顶点数不足以确定) 2)每次添加到批处理时,我将该模型与其自己的纹理一起放入批处理中 3)但如果id不同,我将切换到非实例化状态 4)在帧末,根据需要使用的方法以两种方式呈现。 可以吗? - Ryno
我认为这听起来应该是可行的。我只是想澄清一下,你可以有两个不同的网格,但具有相同数量的顶点。 - MuertoExcobito
好的,完美的,是的我知道这个事实,但我认为使用实例化时,您还应该传递一个包含所有矩阵顶点的VBO,然后指定如何将其“切割”到GPU。例如,对于十个立方体,传递一个360个顶点的VBO,并告诉GPU每次取36个顶点。在这种情况下,我认为即使是相同编号的网格也可以工作,但可能实例化只需要一个单独的网格作为参数和一个N数字来完成工作,我还没有尝试过,所以我不知道。感谢您的回答,现在我的想法更加清晰了。 - Ryno
显示剩余4条评论

0

除了实例化和添加另一个顶点属性作为某个对象ID之外,我还想提及另一种策略(需要现代OpenGL):

扩展ARB_multi_draw_indirect(自GL 4.3以来已成为核心)添加了间接绘图命令。这些命令直接从另一个缓冲区对象中获取其参数(顶点数、起始索引等)。使用这些函数,可以通过单个绘制调用绘制许多不同的对象。

然而,由于您仍然希望有一些每个对象的状态,例如变换矩阵,因此该功能还不足够。但是与ARB_shader_draw_parameters(尚未成为核心GL)结合使用,您将获得gl_DrawID参数,该参数将在一个多重绘制间接调用中为每个单个对象递增一次。这样,您就可以索引到某个UBO、TBO、SSBO(或其他任何存储每个对象数据的地方)。


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