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

8
我正在使用OpenGL和GLSL开发一个小型3D引擎。我目前使用纹理缓冲对象(TBOs)来存储所有矩阵(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。因此,为了避免在顶点着色器中进行无用的计算,我决定将每个网格节点的ModelViewProjMatrix、ModelViewMatrix和ModelMatrix嵌入到TBO中,如下所示:

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。因此,我将失去proj/view矩阵信息,因为我将无法连接到正确的TBO,这就带来了我的第三种方法。
3)将投影和视图矩阵存储在一个TBO中,将所有其他模型矩阵存储在另一个或多个TBO中,如下所示:
TBO_0 = {[ProjMatrix][ViewMatrix]} TBO_1 = {[ModelMatrix_Box1][ModelMatrix_Box2]...}
你觉得我的三种方法怎么样?哪一种对你来说最好?
非常感谢您的帮助!

你为什么使用TBO而不是Uniform Buffer?是因为大小限制吗? - Jerem
1个回答

6
解决方案3是大多数引擎使用的方法,除了它们使用统一缓冲区(常量缓冲区)而不是纹理缓冲区。此外,它们通常不会将所有模型矩阵都放在同一个缓冲区中,而是按对象类型进行分组(因为相同对象使用实例化一次性绘制),有时还会按更新频率进行分组(从不移动的对象放在同一缓冲区中,以便无需更新)。
此外,glBufferSubData可能会非常慢;更新缓冲区通常比绑定不同的缓冲区要慢,因为驱动程序内部发生了所有同步。有一本非常好的书章节可以免费在互联网上获得,名为“OpenGL Insights: Asynchronous Buffer Transfers”(搜索即可找到)。
编辑:您在评论中提供的nvidia文章非常有趣。他们建议使用glMultiDrawElements一次性进行多个绘制调用(这是主要技巧,其他所有内容都是因为这个决定而存在的)。这可以大大减少驱动程序中的CPU工作量,但这也意味着必须提供绘制对象所需的所有数据更加复杂:您必须构建/更新矩阵/材料值的更大缓冲区,并且还需要使用类似无绑定纹理的东西才能为每个对象使用不同的纹理。因此,这很有趣,但更加复杂。
而且,只有当您想要绘制许多不同的对象时,glMultiDrawElements才很重要。他们的示例具有68000-98000个不同的网格,这确实很多。例如,在游戏中,通常会有许多相同对象的实例,但最多只有几百个不同的对象。最终,这取决于您的3D引擎需要渲染什么。

你好Jeremy,感谢您的回复。我决定使用TBO而不是UBO(仅用于材料和光参数),因为我在NVIDIA上读到了一篇文章。也许你已经读过了。这是链接http://on-demand.gputechconf.com/gtc/2014/presentations/S4379-opengl-44-scene-rendering-techniques.pdf(您可以从第31页幻灯片中访问TBO矩阵存储)。您能告诉我您对这篇文章的看法吗?提前致谢! - user1364743
在第34页幻灯片中,他们说TBO用于存储大型/随机访问数据,而UBO仅用于频繁更改和非分散访问,这到底是什么意思?我们可以将矩阵视为大型数据吗?因此,在阅读本文后,您认为最适合用于存储矩阵的是TBO还是UBO?您是否仍然选择UBO? - user1364743
好的。所以,如果我使用UBO来存储我的材质参数和光照设置,使用TBO来存储矩阵(一个用于相机变换,另一个用于存储阴影矩阵,最后还有其他一些用于存储模型矩阵(按模型类型或其他标准进行划分)),那么这应该是一个正确的选择。你觉得怎么样?或者你建议我只使用UBO?提前谢谢。 - user1364743
2
很难说。除非你做一些皮肤处理,否则矩阵不应该有分歧的访问方式,但如果你需要超过1024个矩阵(即64KB),那么我想TBO是正确的选择。 - Jerem
好的,非常感谢您的所有解释!再见。 - user1364743
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接