带有移动部件的模型的OpenGL VAO/VBO结构是什么?

9

我来自这个问题:

opengl vbo advice

我使用OpenGL 3.3,不想使用已弃用的功能。我正在使用Assimp导入我的Blender模型。但是我有点困惑于在VAO和VBO方面应该如何分割它们。

首先是一个小问题。我使用glDrawElements,这意味着我不能交错我的顶点属性吗?还是VAO可以通过glVertexAttribPointer和glDrawElements偏移量来确定我的顶点位置?

主要问题可能是,如何为具有多个移动部件和每个部件的多个网格的模型构建VAO / VBO结构。

Assimp中的每个节点都可以包含多个网格,其中每个网格都有纹理、顶点、法线、材质等。 Assimp中的节点包含变换。比如说,我有一艘船上有一个炮塔。我想能够旋转炮塔。这是否意味着我将使船节点成为单独的VAO,并使用包含其属性的每个网格的VBO(或多个VBO等)?我猜就像这样

draw(ship);    //call to draw ship VAO
pushMatrix(turretMatrix)  //updating uniform modelview matrix for the shader
draw(turret);  //call to draw turret VAO

我还不完全理解UBO(统一缓冲对象),但它似乎可以传递多个uniform,这是否有助于将可移动部件的完整模型包含在单个VAO中?

2个回答

15

首先,VAO只“记住”最后一个顶点属性绑定(和索引缓冲区的VBO绑定(如果有的话))。因此它不会记住在glDrawElements()中的偏移量,你需要在使用VAO时稍后调用它。它也不会阻止你使用交错的顶点数组。让我来解释一下:

int vbo[3];
glGenBuffers(3, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, data0, size0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, data1, size1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data2, size2);
// create some buffers and fill them with data

int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// create a VAO

{
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // not saved in VAO
    glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * sizeof(float), NULL); // this is VAO saved state
    glEnableVertexAttribArray(0); // this is VAO saved state
    // sets up one vertex attrib array from vbo[0] (say positions)

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // not saved in VAO
    glVertexAttribPointer(1, 3, GL_FLOAT, false, 5 * sizeof(float), NULL); // this is VAO saved state
    glVertexAttribPointer(2, 2, GL_FLOAT, false, 5 * sizeof(float), (const void*)(2 * sizeof(float))); // this is VAO saved state
    glEnableVertexAttribArray(1); // this is VAO saved state
    glEnableVertexAttribArray(2); // this is VAO saved state
    // sets up two more VAAs from vbo[1] (say normals interleaved with texcoords)

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[2]); // this is VAO saved state
    // uses the third buffer as the source for indices
}
// set up state that VAO "remembers"

glBindVertexArray(0); // bind different vaos, etc ...

稍后查看这个问题,以便在有更多信息时提供答案。

glBindVertexArray(vao); // bind our VAO (so we have VAAs 0, 1 and 2 as well as index buffer)
glDrawElements(GL_TRIANGLE_STRIP, 57, GL_UNSIGNED_INT, NULL);
glDrawElements(GL_TRIANGLE_STRIP, 23, GL_UNSIGNED_INT, (const void*)(57 * sizeof(unsigned int)));
// draws two parts of the mesh as triangle strips

因此,你可以使用单个VAO和一个或多个VBO来绘制交错的顶点数组,使用glDrawElements方法。回答你问题的第二部分,你可以为网格的不同部分有不同的VAO和VBO(这样绘制独立的部分很容易),或者将所有内容融合到一个VAO VBO对中(这样就不需要经常调用glBind*()),然后使用多个glDraw*()调用来绘制网格的各个部分(如上面的代码所示 - 想象一下第一个glDrawElements()绘制船体,第二个绘制炮塔,你只需要在调用之间更新一些矩阵统一变量)。

由于着色器可以在uniforms中包含多个modelview矩阵,您还可以将mesh id编码为另一个顶点属性,并让顶点着色器根据该属性选择要使用的矩阵来转换顶点。这个想法也可以扩展到每个单个顶点使用多个矩阵,其中为每个矩阵分配一些权重。当动画有机物对象(例如玩家角色)时,这是常用的技术(请搜索“skinning”)。

至于uniform buffer objects,唯一的优势就是可以将大量数据打包到它们中,并且它们可以轻松共享于支持的所有着色器(只需将UBO绑定到任何能够使用它的着色器即可)。除非你要使用具有数千个矩阵的对象,否则使用它们没有实际优势。

此外,我从记忆中编写了上面的源代码。如果有错误/问题,请告诉我...


很酷,我觉得我理解了交错的概念,需要试一下。但是我认为你犯了一个错误,为了确保,第一个glVertexAttribPointer用于定义顶点位置时,我理解顶点位置在vbo[0]中是紧密打包的,但你却给了它一个步长?由于它们没有交错,位置的步长不应该为零吗? - CodeMonkey
嗨,对于步幅的观察很好。但这不是错误。我可以将步幅保留为0,OpenGL会从第二个和第三个参数中知道我有一串3D GL_FLOATS(因此大小为3 * sizeof(float)),或者我可以计算它的步幅。这没有任何问题。进一步澄清-步幅是两个连续顶点地址之间的距离,而不是它们之间的空间(因此0并不意味着顶点之间没有未使用的字节,它意味着OpenGL应该从使用的数据类型和维数数量计算实际步幅)。 - the swine
至于另一个评论,我认为可以使用统一数组安全地传递10个矩阵。使用统一缓冲对象基本上是相同的,唯一的区别是您需要分配缓冲对象,将其加载到矩阵中,并将其绑定到着色器(对我来说,这听起来比调用glUniformMatrix4fv()更费力)。是的,这意味着使用glMultiDraw*()渲染整个模型。如果您愿意,我可以为您编写一个简单的演示...只需让我知道即可。 - the swine
没问题,看起来解析成数组很简单。唯一的问题是我使用GLM数学库,不确定如何与value_ptr()等配合使用,但我会解决的。感谢你的帮助 :) - CodeMonkey
是的,但是你的示例代码看起来像这样: glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // 这是VAO保存的状态 - tauran
显示剩余7条评论

0

@theswine

在VAO初始化期间不绑定它会导致我的程序崩溃,但在绑定VAO之后绑定它可以使程序正确运行。你确定这不是保存在VAO中的吗?

glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // not saved in VAO

(顺便说一句:很抱歉提出一个旧话题,我只是觉得这可能对其他人有用,这篇文章确实如此!(这让我想起来,谢谢!!))


嗨。我刚刚偶然看到了这个,因为当您发布以“@ login”开头的答案时,用户不会收到通知(只有OP会收到通知)。您应该在我的答案中留下评论,我会收到通知。我建议您开始一个新问题,并发布一些最小化的代码来重现您的崩溃。那可能吗?然后您可以在此评论下面留下一个“@ login”评论,我会研究您的问题。 - the swine
抱歉,由于我的声望不够,我无法在您的帖子中发表评论。但是我会尝试为您提供一些内容供您查看(也许只是我的硬件问题?)。 - luveti
通常只是交换的索引或纠结的绑定。即使发生在最好的人身上。有时,元素数组(索引)缓冲区会出现问题,特别是在英特尔显卡上。 - the swine

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