使用纹理缓冲区对象(OpenGL)在图形应用程序中管理矩阵的有效方法

时间:2015-04-16 11:04:57

标签: opengl matrix glsl shader vertex-shader

我正在使用OpenGL和GLSL开发一个小型3D引擎。我目前使用纹理缓冲对象(TBO)来存储我的所有矩阵(Proj,View,Model和Shadow Matrices)。但我做了一些研究,在图形引擎中处理矩阵的最佳方法(我的意思是最有效的方法)没有任何成功。目标是将最大矩阵存储到最小数量的TBO中,并且发生最小的状态更改以及GPU和客户端代码之间的最小交换(glBufferSubData)。

我提出了两种不同的方法(各有利弊):

这是一个场景示例:

1个相机(1个ProjMatrix,1个ViewMatrix) 5盒(5 ModelMatrix)

以下是我使用的简单顶点着色器的示例:

#version 400

/*
** Vertex attributes.
*/
layout (location = 0) in vec4 VertexPosition;
layout (location = 1) in vec2 VertexTexture;

/*
** Uniform matrix buffer.
*/
uniform samplerBuffer matrixBuffer;

/*
** Matrix buffer offset.
*/
uniform int MatrixBufferOffset;

/*
** Output variables.
*/
out vec2 TexCoords;

/*
** Returns matrix4x4 from texture cache.
*/
mat4 Get_Matrix(int offset)
{
    return (mat4(texelFetch(
        matrixBuffer, offset), texelFetch(
            matrixBuffer, offset + 1), texelFetch(matrixBuffer, offset + 2),
                texelFetch(matrixBuffer, offset + 3)));
}

/*
** Vertex shader entry point.
*/
void main(void)
{
    TexCoords = VertexTexture;
    {
        mat4 ModelViewProjMatrix = Get_Matrix(
            MatrixBufferOffset);
        gl_Position = ModelViewProjMatrix  * VertexPosition;
    }
}

1)我目前使用的方法:在我的顶点着色器中,我使用ModelViewProjMatrix(光栅化(gl_Position)所需),ModelViewMatrix(用于光照计算)和ModelMatrix。因此,为了避免在顶点着色器中进行无用的计算,我决定为TBO中内联的每个网格节点存储ModelViewProjMatrix,ModelViewMatrix和ModelMatrix,如下所示:

TBO = {[ModelViewProj_Box1] [ModelView_Box1] [Model_Box1] | [ModelViewProj_Box2] ...}

优点:我不需要为每个顶点着色器计算产品Proj * View * Model(例如ModelViewProj)(矩阵是预先计算的)。

缺点:如果我移动相机,我需要更新所有ModelViewProj和ModelView矩阵。所以,有很多信息需要更新。

2)我想到了另一种方式,我认为更有效:存储一次投影矩阵,一旦视图矩阵,最后每个框场景节点模型矩阵再次这样:

TBO = {[ProjMatrix] [ViewMatrix] [ModelMatrix_Box1] [ModelMatrix_Box2] ...}

所以我的顶点着色器看起来像这样:

#version 400

/*
** Vertex attributes.
*/
layout (location = 0) in vec4 VertexPosition;
layout (location = 1) in vec2 VertexTexture;

/*
** Uniform matrix buffer.
*/
uniform samplerBuffer matrixBuffer;

/*
** Matrix buffer offset.
*/
uniform int MatrixBufferOffset;

/*
** Output variables.
*/
out vec2 TexCoords;

/*
** Returns matrix4x4 from texture cache.
*/
mat4 Get_Matrix(int offset)
{
    return (mat4(texelFetch(
        matrixBuffer, offset), texelFetch(
            matrixBuffer, offset + 1), texelFetch(matrixBuffer, offset + 2),
                texelFetch(matrixBuffer, offset + 3)));
}

/*
** Vertex shader entry point.
*/
void main(void)
{
    TexCoords = VertexTexture;
    {
        mat4 ProjMatrix = Get_Matrix(MatrixBufferOffset);
        mat4 ViewMatrix = Get_Matrix(MatrixBufferOffset + 4);
        mat4 ModelMatrix = Get_Matrix(MatrixBufferOffset + 8);

        gl_Position = ProjMatrix * ViewMatrix * ModelMatrix * VertexPosition;
    }
}

优点:TBO包含使用的确切数量的矩阵。更新是高度针对性的(如果我移动相机我只更新视图矩阵,如果我调整窗口大小,我只更新投影矩阵,最后如果对象移动,则只更新其模型矩阵。)

缺点:我需要计算顶点着色器ModelViewProjMatrix中的每个顶点。另外,如果场景由大量对象组成,每个对象拥有不同的模型矩阵,我可能需要创建一个新的TBO。因此,我将丢失项目/视图矩阵信息,因为我不会连接到正确的TBO,这将我们引入第三种方法。

3)将投影和视图矩阵存储在TBO中,将所有其他模型矩阵存储在另一个或其他TBO中,如下所示:

TBO_0 = {[ProjMatrix] [ViewMatrix]} TBO_1 = {[ModelMatrix_Box1] [ModelMatrix_Box2] ...}

您如何看待我的3种方法?哪一个最适合你?

非常感谢您的帮助!

1 个答案:

答案 0 :(得分:2)

解决方案3是大多数引擎所做的,除了它们使用统一缓冲区(常量缓冲区)而不是纹理缓冲区。此外,他们通常不会将所有模型矩阵一起分组到同一个缓冲区中,它们通常按对象类型分组(因为相同的对象是通过实例化一次绘制的),有时是按更新的频率(从不移动的对象位于同一缓冲区中,因此永远不需要更新)。

glBufferSubData也可能很慢;更新缓冲区通常比仅绑定另一个缓冲区要慢,因为驱动程序内部发生了所有同步。有一本非常好的书籍章节,在互联网上免费提供,名为" OpenGL Insights:Asynchronous Buffer Transfers" (Google可以找到它)。

编辑:您在评论中链接的nvidia article非常有趣。他们建议一次使用glMultiDrawElements进行多次绘制调用(这是主要技巧,因为这个决定,其他所有东西都在那里)。这可以大大减少驱动程序中的CPU工作量,但这也意味着提供绘制对象所需的所有数据要复杂得多:你必须为矩阵/材料值构建/更新更大的缓冲区并且,您还需要使用无绑定纹理之类的东西才能为每个对象提供不同的纹理。所以,有趣,但更复杂。

如果你想绘制很多不同的对象,glMultiDrawElements是很重要的。他们的例子有68000-98000个不同的网格,这真的很多。例如,在游戏中,您通常拥有相同对象的大量实例,但只有几百个不同的对象(最多)。最后,它取决于您的3D引擎需要渲染的内容。