使用多个OpenGL VBO绘制多个模型

14

我不想贴太多的代码,所以我会用直观的语言表达这个问题。希望你能理解我的意思。

我正在制作一个游戏,在代码中有一个模型类来加载模型并设置它的VBO。

在加载函数中,它生成一个新的 VBO ID,并通过绑定等操作将顶点数据从文件加载到缓冲区中(这个过程很顺利)。

在程序开头,我创建了一个模型对象,并加载一个 .obj 文件。

在渲染函数中,我只需调用 DrawArrays() 函数(带有必要的额外内容),如我所说,“一切正常”,因为模型出现在屏幕上没有任何问题。

然而,问题是当我在程序开头创建两个模型对象并加载不同的 .obj 文件到每个对象时。

问题在于当我播放时,只有第二个模型显示在屏幕上。

问题出在我不能正确理解 VBO 的工作原理。

这就是我"认为" VBO 工作的方式。

你可以生成任意数量的 VBO ID。 你可以绑定每个 ID 使其成为活动缓冲区。 你可以将顶点数据加载到该缓冲区中。 现在,您有效地拥有许多不同的 ID,每个 ID 都对应一个“顶点数据集”。 通过调用 DrawArray 函数,它会绘制你生成的所有缓冲区(有效地在屏幕上显示出所有模型)。

现在我知道这是错误的,因为在我的有限理解中,我看不到如何打开/关闭模型。但是,无论我查看多少教程,我都无法回答这个问题,因为它们都只关注于 " 显示第一个 VBO",而这正好是我所能做到的。

所以如果这有意义的话,有人可以 enlighten 我吗?

3个回答

12

你基本做得没错,但是绘图命令并不会绘制所有生成的缓冲区。如果您要绘制具有不同缓冲区中数据的2个对象,则必须发出2个绘图命令。

让我解释一下,基于我自己的做法:

您可以使用glGenBuffers创建缓冲区。在使用缓冲区之前,您必须将其“绑定”,也就是说,使用glBindBuffer将其激活。当缓冲区激活时,您可以对其进行操作(例如填充数据)。请注意,有不同的绑定目标,因此您可以例如拥有一个活动的数组缓冲区(用于顶点数据)和一个活动的元素数组缓冲区(用于索引数据)。

当您想要绘制内容时,您必须指定您要绘制的内容(我现在假设您正在使用自己的着色器)。通常,您至少会指定顶点数据和索引数据。为此,您首先需要绑定顶点数据所在的缓冲区(作为数组缓冲区),然后使用glVertexAttribPointer调用您的属性ID。这告诉OpenGL绑定的缓冲区现在是当前属性的来源。然后,您将索引缓冲区绑定为元素数组缓冲区,并调用glDrawElements(不确定如何在这里使用glDrawArrays)。

您无法同时为同一属性使用多个VBO - 绑定另一个缓冲区的调用将覆盖先前的调用并使第二个缓冲区变为活动状态,这就是为什么只有第二个对象被绘制出来的原因。

您还可以使用VAO简化绘图过程 - 基本上,您可以将一个ID分配给绑定命令,然后可以使用一行代码执行它们(类似于显示列表)。

因此,我的加载(一个对象)看起来像这样:

  1. 创建缓冲区(假设缓冲区1用于顶点数据,缓冲区2用于索引数据)。
  2. 将缓冲区1作为数组缓冲区绑定。
  3. 填充缓冲区以存储顶点数据。
  4. 重复2和3,只不过这次针对缓冲区2作为元素数组缓冲区和索引数据。

而我绘图的过程(同样是一个对象):

  1. 将缓冲区1作为数组缓冲区绑定。
  2. 使用glVertexAttribPointer来指定该缓冲区属于哪个属性。
  3. 将缓冲区2绑定为元素数组缓冲区。
  4. 调用DrawElements函数。

对于第二个对象,您需要重复执行以上步骤。


3
“你不能同时使用多个VBO” —— 没错,但是对于同一属性而言,可以同时使用多个VBO,每个VBO都可以针对不同的属性同时使用(参见https://dev59.com/eWw05IYBdhLWcg3weBru#7223695作为一个例子)。 - mlvljr
1
@mlvljr:当然,你是对的,我编辑了我的答案。我试图让答案尽可能基础 - 我相信,在开始处理法线、纹理坐标、VAO等之前,先掌握基础知识是有帮助的。 - vesan
旧的回答,但你刚刚解决了我的问题。你能详细说明为什么每个对象都需要glVertexAttribPointer吗?在我的情况下,值每次都是相同的,但是如果没有它,对象的位置不会更新。glVertexAttribPointer是否具有重新读取数组的副作用? - Jeffrey

7

按照以下步骤渲染您的模型,假设model1的顶点存储在vertexBuffer中,model2的顶点存储在vertexBuffer2中:

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);        
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, <no. of vert>);

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer2);
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, <no. of vert>);

//swap buffers depending on your windowing toolkit

确保不要在两个模型呈现之间执行glClear。

祝一切顺利!


可以确认这种基本方法是有效的;我在Java中使用lwjgl,而不是调用 glVertexAttribPointer(...),而是调用(在我的特定情况下)glVertexPointer(3,GL_INT,vertexDataSize,0) - bbarker
我确认这种方法在本地Android开发中的OpenGL ES上有效。这是我的源代码:https://github.com/raydelto/opengles-md2loader-android - Raydelto Hernandez

1
你需要做的是在每次调用drawArrays之前将vertexArrayAttribute设置回第一个对象数据。
drawArrays函数使用VAO中存储的绑定来查找渲染所需的数据。
因此,要渲染两个模型,您需要创建第二个VAO并绑定它,并根据需要调用glVertexAttribPointer。要进行绘制,您需要绑定VAO并为每个模型调用drawArrays。

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