tobaroony
tobaroony

Reputation: 35

Passing VBO to shaders with different layouts

If I have a vertex shader that expects this...

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec4 aBoneWeights; 
layout(location = 2) in vec4 aBoneIndices;

How do I pass a a VBO that is already organised for each vertex as

Position(vec3) | Color(vec3) | UV(vec2) | BoneWeight(vec4) | BoneIndex(vec4)

Do I have to make a new VBO? If my vertex data is interlaced, then do I have to create a new buffer of vertex data too?

Upvotes: 3

Views: 1640

Answers (1)

robthebloke
robthebloke

Reputation: 9668

Option 1: Create a different VAO for each shader

The VAO defines a mapping from you shader attributes (e.g. read the vec3's from this memory location in the VBO, with a stride of N bytes, and map it to the attribute bound to location X).

Some global to store the VAO name

GLuint g_vao;

Then to create it (For the data layout you have defined in your shader):

// create the VAO 
glCreateVertexArrays(1, &g_vao);


// set up: layout(location = 0) in vec3 aPos;
glEnableVertexArrayAttrib(g_vao, 0);  //< turn on attribute bound to location 0

// tell OpenGL that attribute 0 should be read from buffer 0
glVertexArrayAttribBinding(
  g_vao, //< the VAO
  0,     //< the attribute index (location = 0)
  0);    //< the vertex buffer slot (start from zero usually)

// tell openGL where within the buffer the data exists
glVertexArrayAttribFormat(
  g_vao,    //< the VAO
  0,        //< the attribute index
  3,        //< there are 3 values xyz
  GL_FLOAT, //< all of type float
  GL_FALSE, //< do not normalise the vectors
  0);       //< the offset (in bytes) from the start of the buffer where the data starts



// set up: layout(location = 1) in vec4 aBoneWeights
glEnableVertexArrayAttrib(g_vao, 1);  //< turn on attribute bound to location 0

// tell OpenGL that attribute 1 should be read from buffer 0
glVertexArrayAttribBinding(
  g_vao, //< the VAO
  1,     //< the attribute index (location = 1)
  0);    //< the vertex buffer slot (start from zero usually)

// tell openGL where within the buffer the data exists
glVertexArrayAttribFormat(
  g_vao,    //< the VAO
  1,        //< the attribute index
  4,        //< there are 4 values
  GL_FLOAT, //< all of type float
  GL_FALSE, //< do not normalise the vectors
  sizeof(float) * 8); //< the offset (in bytes) from the start of the buffer where the data starts



// set up: layout(location = 2) in vec4 aBoneIndices;
glEnableVertexArrayAttrib(g_vao, 2);  //< turn on attribute bound to location 2

// tell OpenGL that attribute 2 should be read from buffer 0
glVertexArrayAttribBinding(
  g_vao, //< the VAO
  2,     //< the attribute index (location = 2)
  0);    //< the vertex buffer slot (start from zero usually)

// tell openGL where within the buffer the data exists
glVertexArrayAttribFormat(
  g_vao,    //< the VAO
  2,        //< the attribute index
  4,        //< there are 4 values xyz
  GL_FLOAT, //< all of type float
  GL_FALSE, //< do not normalise the vectors
  sizeof(float) * 12);       //< the offset (in bytes) from the start of the buffer where the data starts

However, I think your shader definition is wrong for attribute 2 (because you will have to pass the bone indices as floating point data, which feels very wrong to me!).

I'd have thought you'd have wanted integers instead of floats:

layout(location = 2) in ivec4 aBoneIndices;

However when binding to integers, you need to use glVertexArrayAttribIFormat instead of glVertexArrayAttribFormat:

glVertexArrayAttribIFormat(
  g_vao,    //< the VAO
  2,        //< the attribute index
  4,        //< there are 4 indices
  GL_UNSIGNED_INT, //< all of type uint32
  sizeof(float) * 12);   

After all of that, you'd need to bind the vertex buffer to the vertex slot zero you've been using above...

glVertexArrayVertexBuffer(
  g_vao, //< the VAO
  0,     //< the vertex buffer slot
  0,     //< offset (in bytes) into the buffer
  sizeof(float) * 16); //< num bytes between each element

Option 2: Just use the same VAO and the same VBO

Just encode the indices to have specific meanings, and then you can always use the same VAO.

layout(location = 0) in vec3 aPos;
//layout(location = 1) in vec4 aCol;  //< not used in this shader
//layout(location = 2) in vec4 aUv;   //< not used in this shader
layout(location = 3) in vec4 aBoneWeights; 
layout(location = 4) in vec4 aBoneIndices;

/edit In answer to your question, it very much depends on the version of OpenGL you are using. The answer I posted here uses the latest Direct State Access (DSA) extensions found in OpenGL4.5. If you can make use of them, I strongly suggest it.

OpenGL 3.0: glVertexAttribPointer

Yes, this will work. However, it's a mechanism that is strongly tied to OpenGL's bind paradigm. Each attribute is effectively bound to the buffer that was bound when you make the call to glVertexAttribPointer (i.e. you'll be doing: glBindBuffer(); glEnableVertexAttribArray(); glVertexAttribPointer();).

The problem with this is that it kinda locks you into creating a VAO for each VBO (or set of VBOs, if pulling data from more than one), because the attributes are bound to the exact buffer that was bound when you specified that attribute.

OpenGL 4.3: glVertexAttribFormat

This version is much like the one I've presented above. Rather than passing the VAO into the function call, you do a call to glBindVertexArray first (if you search for the docs on the above methods, the ones without the VAO argument simply use the currently bound VAO).

The advantage of this approach over the old API, is that it is trivial to bind the VAO to another VBO(s) [i.e. you can associate a VAO with a GLSL program, rather than having a VAO for each VBO/Program pair]- just a call to glBindVertexBuffer for each VBO, and it'll work nicely.

OpenGL 4.5: glVertexArrayAttribFormat

In the long run, this is by far the easiest version of the API to use. The main advantage is that you don't need to worry about which VAO is currently bound (because you pass it in as an argument). This has a number of advantages, because you no longer care about which VAO has been bound, and it also opens the door to modifying OpenGL objects from a different thread (something the older API versions would not allow).

Upvotes: 3

Related Questions