2
votes

As I understand VAOs/VBOs currently, a VAO retains all the attribute information that has been set up since it was bound, eg. the offset, stride, number of components, etc. of a given vertex attribute within a VBO.

What I seem to be unclear on is how VAOs and VBOs work together. A lot of the examples I have seen specify the vertex attributes with respect to the currently bound VBO, and when the VAO is bound the data in the VBO become accessible. One way I can see of using VAOs in this way would be to have one per object (where each object uses its own VBO), but I've read that this is poor performance-wise because of switching between many VAOs unnecessarily. I also would rather like to avoid having to store all my object data in one monolithic VBO because I will need to add and remove objects within my scene at any time - as a 3D editor, I feel the application would be much better suited to having each geometry object own its own buffer, rather than in some large, preallocated VBO. (Is this a correct assumption?)

My question therefore is whether one VAO can store vertex attribute configurations independently of the VBOs? Would I be able to configure a VAO to expect data in a certain format (eg. position, normal, UV) and then "swap in" different VBOs as I draw the different geometry objects, or is the format information essentially bound only to the VBO itself? If the latter, is it worth me using VAOs at all?

1
Usually you use one VAO per model, setting it up with the vertex and index buffers you need for that model, then binding the VAO when you want to draw the model.Colonel Thirty Two
This is what I meant about poor performance though. I'm expecting potentially thousands of geometry blocks in a world, with portions rendered dependent on camera position, and switching VAO on every block seems like it would render them pointless considering they're designed not to be switched too often.x6herbius
Stop premature optimizing and making assumptions. Code it and see how it performs.Bartek Banachewicz

1 Answers

2
votes

ARB_vertex_attrib_binding allows you to separate Vao attribute format and buffer binding.

https://www.opengl.org/wiki/Vertex_Specification#Separate_attribute_format

Internally, when you configure your Vao, Vertex buffer is automatically associated with attribute index. With ARB_vertex_attrib_binding, you have new gl functions to define Attribute formats independently from the bound buffer, which may be switched with VertexBuffer functions.

Here some piece of code in c# with openTK: (full surce: https://github.com/jpbruyere/GGL/tree/ottd/Tetra )

The solution here is to build a VAO with all your meshes concatenated, keeping for each of them only

  • BaseVertex = the vertice offset in the VAO
  • IndicesOffset = the offset in the Element buffer (ebo index)
  • IndicesCount = and the total indice count of the model

    protected void CreateVAOs()
    {
        //normal vao binding
    
        vaoHandle = GL.GenVertexArray();
        GL.BindVertexArray(vaoHandle);
    
        GL.EnableVertexAttribArray(0);
        GL.BindBuffer(BufferTarget.ArrayBuffer, positionVboHandle);
        GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0);          
    
        ... other attrib bindings come here
    
        //ARB vertex attrib binding use for fast instance buffers switching
        //note that I use 4 attrib indices to bind a matrix
        GL.VertexBindingDivisor (instanceBufferIndex, 1);
        for (int i = 0; i < 4; i++) {                   
            GL.EnableVertexAttribArray (instanceBufferIndex + i);   
            GL.VertexAttribBinding (instanceBufferIndex+i, instanceBufferIndex);
            GL.VertexAttribFormat(instanceBufferIndex+i, 4, VertexAttribType.Float, false, Vector4.SizeInBytes * i);
        }
    
        if (indices != null)
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, eboHandle);
    
        GL.BindVertexArray(0);
    }
    

Then, I define Instances of mesh with just a Matrix array for each, that's a normal buffer creation, but not staticaly bound to the vao.

    instancesVboId = GL.GenBuffer ();       

    GL.BindBuffer (BufferTarget.ArrayBuffer, instancesVboId);
    GL.BufferData<Matrix4> (BufferTarget.ArrayBuffer,
                new IntPtr (modelMats.Length * Vector4.SizeInBytes * 4),
                modelMats, BufferUsageHint.DynamicDraw);
    GL.BindBuffer (BufferTarget.ArrayBuffer, 0);

To render such vao, I loop inside my instance array:

    public void Bind(){
        GL.BindVertexArray(vaoHandle);
    }

    public void Render(PrimitiveType _primitiveType){
        foreach (VAOItem item in Meshes) {
            GL.ActiveTexture (TextureUnit.Texture1);
            GL.BindTexture (TextureTarget.Texture2D, item.NormalMapTexture);
            GL.ActiveTexture (TextureUnit.Texture0);
            GL.BindTexture (TextureTarget.Texture2D, item.DiffuseTexture);
            //Here I bind the Instance buffer with my matrices
            //that's a fast switch without changing vao confing
            GL.BindVertexBuffer (instanceBufferIndex, item.instancesVboId, IntPtr.Zero,Vector4.SizeInBytes * 4);
            //here I draw instanced with base vertex
            GL.DrawElementsInstancedBaseVertex(_primitiveType, item.IndicesCount, 
                DrawElementsType.UnsignedShort, new IntPtr(item.IndicesOffset*sizeof(ushort)),
                item.modelMats.Length, item.BaseVertex);
        }
    }

The final VAO is bound only once.