如何在考虑性能的同时正确管理现代OpenGL中的数据?

时间:2014-11-26 07:23:55

标签: performance opengl vertex-buffer particle-system vertex-array-object

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

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

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

例如,游戏可能包含许多3D模型,并且将每个模型与不同的VAO分开似乎是合适的。

另一方面,粒子系统包含许多彼此独立行进的断开的基元。在这种情况下,每个系统使用单个VAO可能会提高CPU-GPU传输的性能。但是,在这种情况下,原语需要彼此不同地翻译,因此将每个粒子分成非常小的VAO似乎是可行的。

问题:

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

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

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

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

<小时/> VAO的创建在这里有很好的描述:

2 个答案:

答案 0 :(得分: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。 如果你在这两个函数上做了几个谷歌,我相信你会明白实例渲染是如何工作的。 再问一下问题

答案 1 :(得分:1)

一般规则是,您希望最小化绘制调用的数量。如果您将事物放入单独的VAO中,则必须为每个VAO执行绘制调用。此外,在VAO和VBO之间切换还需要付出代价。不要将VAO和VBO视为&#34;模型&#34;容器,但作为内存池,应使用每个VBO / VAO来合并相同属性的数据。

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