3

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?

tobaroony
  • 35
  • 3
  • What do you mean by "a new VBO"? Are you talking about copying the data or using a different vertex format? Also, if you want to share vertex formats with different shaders, it's best to make the location for each attribute represent the kind of data in question. For example, you could assign location 1 to always mean bone weights. – Nicol Bolas Mar 13 '20 at 03:12
  • Thanks Nicol. I appreciate your answer. – tobaroony Mar 13 '20 at 05:06
  • It's a comment, not an answer. And you're supposed to provide the information requested (in your question). – Nicol Bolas Mar 13 '20 at 05:09
  • Sorry. I'm new to this. by "new VBO" I meant copying portions of the interlaced data into a new VBO. (I was alos thanking you for your suggestion to assign certain locations to always mean the same thing) – tobaroony Mar 13 '20 at 05:23

1 Answers1

2

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).

robthebloke
  • 9,331
  • 9
  • 12
  • Thanks for this. Great stuff! For option 1, could you just use glVertexAttribPointer(location, attributeSize, type, isNormalized, stride, (void*)offset); instead where offset is the position in the interlaced vbo buffer? – tobaroony Mar 13 '20 at 05:13
  • yes you can, but the VBO would now be bound to the VAO. To rebind to a different VBO, you'll pretty much have to recreate the VAO. I've added some extra info above. – robthebloke Mar 13 '20 at 06:22