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

Efficient way to manage matrices within a graphic application using Texture Buffer Object(s) (OpenGL)

我正在使用 OpenGL 和 GLSL 开发一个小型 3D 引擎。我目前使用纹理缓冲区对象 (TBO) 来存储我所有的矩阵(投影、视图、模型和阴影矩阵)。但是我做了一些关于在图形引擎中处理矩阵的最佳方式(我的意思是最有效的方式)的研究,但没有成功。目标是将最多的矩阵存储到最少数量的 TBO 中,并发生最少的状态更改以及 GPU 和客户端代码 (glBufferSubData) 之间的最少交换。

我提出了两种不同的方法(各有优缺点):

这是一个场景示例:

1 个摄像头(1 个 ProjMatrix,1 个 ViewMatrix) 5 个盒子(5 个模型矩阵)

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

#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 包含使用的矩阵的确切数量。更新具有高度针对性(如果我移动相机,我只更新视图矩阵,如果我调整 window 的大小,我只更新投影矩阵,最后如果一个对象正在移动,只更新它的模型矩阵)。

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

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

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

你觉得我的3种方法怎么样?哪一个最适合你?

非常感谢您的帮助!

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

glBufferSubData 也可能很慢;更新一个缓冲区通常比绑定一个不同的缓冲区要慢,因为所有的同步都发生在驱动程序内部。有一个很好的书籍章节,可以在 Internet 上免费获得,称为 "OpenGL Insights: Asynchronous Buffer Transfers"(Google 它可以找到它)。

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

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