Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm developping a little 3D Engine using OpenGL and GLSL. I currently use Texture Buffer Objects (TBOs) to store all my matrices (Proj, View, Model and Shadow Matrices). But I did some researches on what is the best way to handle matrices (I mean the most efficient way) within a graphic engine, without any success. The goal is to store a maximum of matrices into a minimum number of TBO and occur a minimum of state changes and a minimum of exchanges between the GPU and client code (glBufferSubData).

I propose 2 different methods (with their advantages and disadvantages):

Here's a scene example:

1 Camera (1 ProjMatrix, 1 ViewMatrix) 5 boxes (5 ModelMatrix)

Here's an example of a simple vertex shader I use:

#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) The method I currently use: in my vertex shader I use to use ModelViewProjMatrix (needed for rasterization(gl_Position)), ModelViewMatrix (for lighting calculations) and ModelMatrix. So to avoid useless calculation within the vertex shader I've decided to store the ModelViewProjMatrix, the ModelViewMatrix and the ModelMatrix for each mesh node inlined in the TBO as follow:

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

Advantages: I don't need to compute the product Proj * View * Model (ModelViewProj for example) for each vertex shader (the matrices are pre-calculated).

Disadvantages: if I move the camera I need to update all the ModelViewProj and ModelView matrices. So, a lot of informations to update.

2) I thought about an other way, I think more efficient: store once the projection matrix, once the view matrix and finally each box scene node model matrix once again this way:

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

So my vertex shader will look like this:

#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;
    }
}

Advantages: The TBO contains the exact number of matrices used. The update is highly targeted (if I move the camera I only updates the view matrix, if I resize the window I only updates the projection matrix and finally if a object is moving only its model matrix will be updated).

Disadvantages: I need to compute fo each vertex within the vertex shader the ModelViewProjMatrix. Plus, if the scene is composed of a huge number of object with each of them owning a different model matrix, I probably need to create a new TBO. Consequently, I will loose the proj/view matrix information because I won't be connect to the right TBO, which bring us to my third method.

3) Store the Projection and View matrix in a TBO and all the other model matrices within another or others TBO(s) as follow:

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

What do you think of my 3 methods ? Which one is the best for you?

Thanks a lot in advance for your help!

like image 389
user1364743 Avatar asked Apr 16 '15 11:04

user1364743


People also ask

What is the use of buffer in OpenGL?

Buffer objects are handles to OpenGL-managed memory. Among other things, they are used to store vertex arrays (using the GL_ARRAY_BUFFER target) and element arrays (using the GL_ELEMENT_ARRAY_BUFFER target).

What is a buffer texture?

A Buffer Texture is a one-dimensional Texture whose storage comes from a Buffer Object. They are used to allow a shader to access a large table of memory that is managed by a buffer object.

How do I use a buffer texture in GLSL?

In GLSL, buffer textures can only be accessed with the texelFetch function. This function takes pixel offsets into the texture rather than normalized texture coordinates. The sampler types for buffer textures are gsamplerBuffer . Buffer textures have a size limit, separate from the standard one-dimensional texture size.

Can glteximage call a buffer texture?

Buffer textures are defined by the fact that the storage for the texels comes directly from a buffer object. Therefore, glTexImage calls are not appropriate for buffer textures; calling any of the gl*Image functions on the GL_TEXTURE_BUFFER will result in an error.


1 Answers

The solution 3 is what most engines do, except they use uniform buffers (constant buffers) instead of texture buffers. Also they don't generally group all the model matrices together in the same buffer, they usually are grouped by object type (because same objects are drawn at once with instancing) and sometimes by frequency of update (objects that never move are in the same buffer so that it never needs to be updated).

Also glBufferSubData can be pretty slow; updating a buffer is often slower than just binding a different one, because of all the synchronization happening inside the driver. There is a very good book chapter about that, freely available on the Internet, called "OpenGL Insights: Asynchronous Buffer Transfers" (Google it to find it).

EDIT: The nvidia article you linked in the comments is very interesting. They recommend using glMultiDrawElements to make several draw calls at once (that's the main trick, everything else is there because of that decision). That can reduce the CPU work in the driver a lot, but that also mean that it's a lot more complicated to provide all the data required to draw the objects: you have to build/update bigger buffers for the matrices / material values and, you also need to use something like bindless textures to be able to have different textures for each object. So, interesting, but more complicated.

And glMultiDrawElements is only important if you want to draw a lot of different objects. Their examples have 68000-98000 different meshes, that's really a lot. In a game, for example, you usually have lots of instances of the same objects, but only a few hundred of different objects (maximum). In the end, it depends on what your 3D engine needs to render.

like image 79
Jerem Avatar answered Oct 02 '22 08:10

Jerem