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

use*_*743 5 opengl shader glsl matrix 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

1)我目前使用的方法:在我的顶点着色器中,我使用ModelViewProjMatrix(光栅化(gl_Position)所需),ModelViewMatrix(用于光照计算)和ModelMatrix.因此,为了避免在顶点着色器中进行无用的计算,我决定在TBO中内联每个网格节点的ModelViewProjMtrix,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

非常感谢您的帮助!

Jer*_*rem 6

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

glBufferSubData 也可能非常慢;更新缓冲区通常比绑定不同的缓冲区慢,因为所有同步都发生在驱动程序内部。关于这一点,有一个很好的书籍章节,可以在互联网上免费获得,名为“OpenGL Insights:异步缓冲区传输”(Google 即可找到)。

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

仅当您想要绘制许多不同的对象时,glMultiDrawElements 才重要。他们的示例有 68000-98000 个不同的网格,这确实很多。例如,在游戏中,通常有许多相同对象的实例,但只有几百个不同的对象(最多)。最终,这取决于您的 3D 引擎需要渲染什么。

  • 很难说。你不应该对矩阵有不同的访问权限(除非你做了一些蒙皮),但如果你需要超过 1024 个矩阵(即 64KB),那么我想 TBO 是最好的选择。 (2认同)