绑定到OpenGL 3.x VBO

8
我正在尝试更新我的引擎,它曾经使用OpenGL 2.x风格的顶点数组,现在需要更新到VAOs/VBOs以适应OpenGL 3.x。我认为我没有正确地绑定VBO。请阅读下面获取更多信息或跳转到代码并查找我的错误。
我的网格类快速概述如下:
Mesh
- 根MeshNode MeshNode - 变换 - VAO索引 - 索引VBO索引 - 子MeshNode数组 - MeshObjects数组
MeshObject
- 所有从文件加载的单个总网格的顶点和索引数据 - 顶点VBO索引
如果我只用一个MeshObject绘制MeshNode,它似乎可以正常绘制。当我使用多个MeshObjects绘制MeshNode时,我得到的是模型的大致形状,但有些混乱。
我已经检查了Visual Studio调试器中的顶点数据和gDEbugger中的VBO数据,一切看起来都很好,所以我相信从文件加载和加载到VBO是有效的。
我使用gDEbugger强制将所有顶点绘制为点而不是三角形,它具有单个MeshObject的形状,这使我相信我只是没有正确地绑定到不同的VBO。就像每次都尝试使用不同的索引,但是相同的顶点。
顶点数据如下:
struct VertexData
{
    enum
    {
        NUM_TEXCOORDS = 1,
    };
    vector3 vertex;
    vector3 normal;
    vector2 texCoord[NUM_TEXCOORDS];
};

相关的MeshNode代码:

void MeshNode::initVAO(void)
{
    closeVAO();

    unsigned int scan;

    //init index data
    if (m_meshObjects.size() > 0)
    {
        glGenVertexArrays(1, &m_meshVAO);
        glBindVertexArray(m_meshVAO);
        {
            //add up the total index count for all the mesh objects in this node
            unsigned int indexCount = 0;
            for (scan = 0; scan < m_meshObjects.size(); ++scan)
            {
                indexCount = indexCount + m_meshObjects[scan].getIndices()->size();
            }
            //make the actual index buffer
            glGenBuffers(1, &m_indexVBO);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexVBO);
            {
                glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(unsigned short), NULL, GL_STATIC_DRAW);

                //set up VBOs and fill the index buffer with the index data from each mesh object
                unsigned int offset = 0;
                for (scan = 0; scan < m_meshObjects.size(); ++scan)
                {
                    m_meshObjects[scan].initVBOs(offset);
                }
            }
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
        glBindVertexArray(0);
    }
    for (scan = 0; scan < m_childMeshNodes.size(); ++scan)
    {
        m_childMeshNodes[scan]->initVAO();
    }
}

void MeshNode::closeVAO(void)
{
    if (m_meshVAO != 0)
    {
        glBindVertexArray(m_meshVAO);
        {
            glDeleteBuffers(1, &m_indexVBO);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }
        glBindVertexArray(0);
        glDeleteVertexArrays(1, &m_meshVAO);
        m_meshVAO = 0;
        m_indexVBO = 0;
    }
}

void MeshNode::render(const matrix4 &_parentTransform)
{
    matrix4 transform = _parentTransform * m_transform;

    if (m_meshObjects.size() > 0)
    {
        glBindVertexArray(m_meshVAO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexVBO);
        {
            for (unsigned int objectScan = 0; objectScan < m_meshObjects.size(); ++objectScan)
            {
                m_meshObjects[objectScan].render(transform);
            }
        }
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }

    for (unsigned int childScan = 0; childScan < m_childMeshNodes.size(); ++childScan)
    {
        m_childMeshNodes[childScan]->render(transform);
    }
}

相关的 MeshObject 代码:

void MeshObject::initVBOs(unsigned int& _indexOffset)
{
    //sub in this section of the index data
    m_indexOffset = _indexOffset;
    _indexOffset = _indexOffset + m_indices.size();
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, m_indexOffset * sizeof(unsigned short), m_indices.size() * sizeof(unsigned short), &(m_indices[0]));

    //init vertex data
    glGenBuffers(1, &m_vertexVBO);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexVBO);
    {
        glBufferData(GL_ARRAY_BUFFER, m_data.size() * sizeof(VertexData), &(m_data[0]), GL_STATIC_DRAW);

        glVertexAttribPointer(Shader::POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (char*)0);
        glEnableVertexAttribArray(Shader::POSITION);
        glVertexAttribPointer(Shader::NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (char*)12);
        glEnableVertexAttribArray(Shader::NORMAL);
        glVertexAttribPointer(Shader::TEXCOORD0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), (char*)24);
        glEnableVertexAttribArray(Shader::TEXCOORD0);
    }
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void MeshObject::closeVBOs(void)
{
    glDeleteBuffers(1, &m_vertexVBO);

    m_vertexVBO = 0;
}

void MeshObject::render(const matrix4& _transform)
{
    m_material->bind(_transform);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexVBO);
    {
        glEnableVertexAttribArray(Shader::POSITION);
        glEnableVertexAttribArray(Shader::NORMAL);
        glEnableVertexAttribArray(Shader::TEXCOORD0);
        glDrawRangeElements(GL_TRIANGLES, m_indexOffset, m_indexOffset + m_indices.size(), m_indices.size(), GL_UNSIGNED_SHORT, (char*)0);
        glDisableVertexAttribArray(Shader::POSITION);
        glDisableVertexAttribArray(Shader::NORMAL);
        glDisableVertexAttribArray(Shader::TEXCOORD0);
    }
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

2
+1:对于这么详细的描述表示赞赏,这让找到解决方案变得更加容易了。 - Nicol Bolas
2个回答

5
一个快速概述我的网格类如下所示:
我认为你的场景图层次结构有点混乱。在场景图中,Node所需要的只是变换、Node子级列表以及要在该Node中绘制的网格列表。它不应该有元素缓冲区或VAO;它们在概念上是网格数据的一部分。
确实,你的问题来自于最后一点。Vertex Array Objects 包含由glVertexAttrib(I)Pointer调用设置的状态。这意味着,在同一MeshNode中每次调用MeshObject::initVBOs时,你都会覆盖之前调用设置的数据。
每个网格需要自己的VAO,而不是节点。网格可以共享索引数据,这似乎是你正在做的(但如果你担心有太多的缓冲区,它们也应该共享相同的顶点数据缓冲对象)。但VAO需要不同。多个VAO可以引用相同的元素缓冲区。

好眼力,完全忽略了那个。我实际上搜索过这样的错误,但就是找不到它。 - Christian Rau
@Nicol 在你的回答和尤其是对Christian回答的评论之间,VAOs比花费三天时间研究OpenGL文档和各种在线教程更有意义。真希望早些看到一些不会把这些信息埋在10页废话中的地方。非常感谢。 - Apostate
1
@Apostate:我说的一切都包含在我链接的VAO文章中。OpenGL Wiki是一个相当不错的信息来源。此外,大多数教程都是按顺序阅读的;它们不是针对特定主题的可搜索信息缓存。 - Nicol Bolas
@Nicol:现在我理解了,回顾一下,确实所有内容都在那里。他们只是花了几页纸将其描述为结构,而实际上他们只需要说:VAO处理状态变化,您根据当前绑定的VBO修改VAO,并且它们处理x、y和z功能。重要的部分都被埋在底部,在他们向您保证它们非常令人困惑之后。而且没有示例代码。我只是喜欢简短明了的东西,我想。 - Apostate

1

我认为你在错误地使用glDrawRangeElementsstartend参数是可能出现在索引数组中的最小和最大顶点索引,但你提供了m_indexOffestm_indexOffset+m_indices.size(),这是你渲染的索引数据范围,而不一定是那些索引数组内部的顶点索引范围。

另外,VAO封装了每个顶点数组/缓冲区状态、所有缓冲区绑定、指针和启用标志,因此你进行了许多不必要的调用。只需在initVAO方法中绑定索引缓冲区,然后当你在MeshNode::render方法中绑定VAO时,它总是被绑定(当然,在这种情况下省略所有glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)调用)。同样,在initVAO方法中只需要启用属性数组(假设所有的MeshObject都启用了相同的属性),它们会在绑定VAO时自动启用。在你当前的配置中,VAO是完全不必要的,你没有从中获得任何好处。


VAO不能封装“所有的缓冲绑定”。它仅封装GL_ELEMENT_ARRAY_BUFFER缓冲绑定;甚至GL_ARRAY_BUFFER也没有直接封装。只有glVertexAttrib(I)PointerglEnable/DisableVertexAttribArrayglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *)调用会影响绑定的VAO。 - Nicol Bolas

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