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



我正在使用OpenGL和GLSL开发一个小的3D引擎。我目前使用纹理缓冲区对象(TBO)来存储我的所有矩阵(项目、视图、模型和阴影矩阵)。但我对图形引擎中处理矩阵的最佳方式(我指的是最有效的方式)进行了一些研究,但没有取得任何成功。目标是将最大的矩阵存储到最小数量的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]|[ModelViewProj_Box2]…}

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

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

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

TBO={[ProjMatrix][ViewMatrix][ModelMatrix_Box1][ModelMatrix_Box 2]…}

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

#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][ModelMatric_Box2]…}

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

提前非常感谢您的帮助!

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

此外,glBufferSubData可能非常慢;更新缓冲区通常比绑定不同的缓冲区慢,因为所有的同步都发生在驱动程序内部。关于这一点,有一本非常好的书,在互联网上免费提供,名为"OpenGL Insights:Asynchronous Buffer Transfers"(谷歌搜索)。

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

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

最新更新