0

在现代 OpenGL (3.x+) 中,您创建包含顶点属性的缓冲区对象,例如位置、颜色、法线、纹理坐标和索引。

然后将这些缓冲区分配给相应的顶点数组对象 (VAO),该对象本质上包含指向所有数据以及数据格式的指针。

有很多关于如何创建 VAO 以及如何使用它的教程;不幸的是,尚不清楚应该如何将 VAO 用于大型应用程序或游戏。

例如,一个游戏可能包含许多 3D 模型,用不同的 VAO 分隔每个模型似乎是合适的。

另一方面,粒子系统包含许多相互独立的不连贯图元。在这种情况下,每个系统使用单个 VAO 可能会提高 CPU-GPU 传输的性能。然而,在这种情况下,基元需要以不同的方式进行转换,因此将每个粒子分离成一个非常小的 VAO 似乎是可行的。

问题:

  • 对于大量的小数据集(例如四边形粒子系统),应该将所有数据打包到1个VAO中还是分成多个VAO?每种方法的性能优势/缺点是什么?

假设使用 1 个 VAO,转换每个独立数据子单元的唯一明显方法是修改实际位置信息并将其重新加载到 GPU 中。多次这样做在时间性能方面是昂贵的。

假设使用了许多 VAO,那么 GPU 必须为每个 VAO 存储重复的格式化信息。就空间而言,这似乎很昂贵(但我不确定这是否一定很慢)。

旁注:
是的,我个人对管理粒子系统很感兴趣。为了使这个问题更通用,对其他人更有用,我在询问整个 VAO 管理。我很好奇在考虑存储的数据类型以及需要什么样的性能(时间/空间)时,哪种管理方法比其他管理方法更合适。


VAO 的创建在这里有很好的描述:

4

2 回答 2

2

在粒子的情况下,最好使用实例渲染 - 您可以在一次绘制调用中渲染所有粒子,但为每个粒子分配不同的位置作为属性。您可以使用 glSubData 更新现有缓冲区。这样您就可以在帧之间更新 CPU 端的位置,然后更新缓冲区。

在更复杂的示例中,您可以实例化您想要的任何属性。

我调用实例化渲染并在我的代码中进行设置的方式如下:

void CreateInstancedAttrib(unsigned int attribNum,GLuint VAO,GLuint& posVBO,int numInstances){
    glBindVertexArray(VAO);
    posVBO = CreateVertexArrayBuffer(0, sizeof(vec3),numInstances,GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(attribNum);
    glVertexAttribPointer(attribNum, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), 0);
    glVertexAttribDivisor(attribNum, 1); 
    glBindVertexArray(0);
}

其中 posVBO 是通常的属性数据,下面的行为位置设置缓冲区。渲染时:

void RenderInstancedStaticMesh(const StaticMesh& mesh, MaterialUniforms& uniforms,const vec3* positions){

    for (unsigned int meshNum = 0; meshNum < mesh.m_numMeshes; meshNum++){

        if (mesh.m_meshData[meshNum]->m_hasTexture){
            glBindTexture(GL_TEXTURE_2D, mesh.m_meshData[meshNum]->m_texture);
        }

        glBindVertexArray(mesh.m_meshData[meshNum]->m_vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, mesh.m_meshData[meshNum]->m_instancedDataBuffer);
        glBufferSubData(GL_ARRAY_BUFFER,0, sizeof(vec3) * mesh.m_numInstances, positions);
        glUniform3fv(uniforms.diffuseUniform, 1, &mesh.m_meshData[meshNum]->m_material.diffuse[0]);
        glUniform3fv(uniforms.specularUniform, 1, &mesh.m_meshData[meshNum]->m_material.specular[0]);
        glUniform3fv(uniforms.ambientUniform, 1, &mesh.m_meshData[meshNum]->m_material.ambient[0]);
        glUniform1f(uniforms.shininessUniform, mesh.m_meshData[meshNum]->m_material.shininess);
        glDrawElementsInstanced(GL_TRIANGLES, mesh.m_meshData[meshNum]->m_numFaces * 3, 
                                GL_UNSIGNED_INT, 0,mesh.m_numInstances);
    }
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

这需要考虑很多,但重要的几行是 DrawElementsInstance 和 glBufferSubData。如果您对这两个功能都进行了一些谷歌搜索,我相信您会了解实例化渲染的工作原理。还有问题请追问

于 2014-11-26T07:57:59.007 回答
1

一般规则是,您希望尽量减少绘图调用的数量。如果你把东西放到单独的 VAO 中,你必须为每个 VAO 执行一次绘制调用。此外,在 VAO 和 VBO 之间切换也是有成本的。不要将 VAO 和 VBO 视为“模型”容器,而是将其视为内存池,每个 VBO / VAO 都应该用于合并具有相同属性的数据。

粒子系统是将所有内容放入单个 VBO/VAO的完美候选者。在通常情况下使用实例渲染,其中 VBO 包含有关放置每个粒子的位置的信息。

于 2014-11-26T09:14:53.263 回答