Slicing up vertex data: is it possible in opengl?

Hello kind souls,

I’m still trying to get to a firmer undestanding of how the OpenGL guts work, and I’ve got a couple of oddball questions.

First, I know why the short answer is “But nobody would WANT to do that”. These are questions designed to help me understand how OpenGL works, not things I’d want to do. I think the answer is “No”, but I want to make sure.

Most of the examples show vertex data (XYZ, Normal, Color, UV) passed down as a single set of interleaved floats: XnYnZnUnVnRnGnBn for the XYZ, UV and RGB values of the first vertex, followed by the same sequence for the second vertex, etc. All the examples pass this data down in a single VBO.

Question 1: Is it possible to (instead) pass down the XYZ data in one VBO, the UV data in a second VBO, and the color in a third?

Question 2: Is it possible to pass the first 500 vertexes down in one VBO, and the next 500 vertices down in a second VBO?

My suspicion after reading some of the 4.5 manual is that the answer to both of these questions is “Whether you wanted to or not, and whether that might be reasonable or not, OpenGL needs all of the vertex data stored in one VBO, so the answer is ‘No’”.

Can someone confirm that for me?

-Jeff

The answer is “yes” and “yes”.

For the first case you just need a glBindBuffer with the appropriate buffer object before each glVertexAttribPointer call.

For the second case you would need to split your drawing into 2 draw calls.

[QUOTE=mhagain;1287913]

For the first case you just need a glBindBuffer with the appropriate buffer object before each glVertexAttribPointer call.

For the second case you would need to split your drawing into 2 draw calls.[/QUOTE]

Awesome. Thanks for the quick reply, Mhagain. For what it’s worth, I understood the 2 draw calls… I meant with one. But leaving that aside for a moment…

Let me set up a test case: Suppose we pass in three buffers, containing XYZ coordinates, an index array, and a vertex color array:


public draw( int vertices, FloatBuffer xyz_in, IntBuffer idx_in, FloatBuffer rgb_in) {
  int vao = glGenVertexArray(vao);
  glBindVertexArray (vao); // Does this have to happen before glGenBuffer calls? I don't think so.
                                      // Does it have to happen befire the glBindBuffer calls? Dunno.

  int index_buf = glGenBuffers(); //allocate a vbo for the xyz data
  int index_buf = glGenBuffers(); //allocate a vbo for the index data
  int color_buf = glGenBuffers(); // allocatea a vbo for the color data

  assert (vertices == xyz_ in.length / 3); // 3 floats per vertex
  assert (vertices == rgb_in.length / 3); // 3 floats per vertex
  assert (vertices >= idx_in.length); // No more indices than vertices. That would be silly.

  // put the xyz data into the first (position) vbo
  glBindBuffer(GL_ARRAY_BUFFER, xyz_buf); 
  glBufferData(GL_ARRAY_BUFFER, xyz_in, vertices * 3 * sizeof(float) , GL_STATIC_DRAW);

  // put the color data into the second (color) vbo
  glBindBuffer(GL_ARRAY_BUFFER, color_buf);
  glBufferData(GL_ARRAY_BUFFER, color_in, vertices * 3 * sizeof(float) , GL_STATIC_DRAW);

  // put the index data into the third (index) vbo.
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_in, idx_in.length() * sizeof(unsigned int), GL_STATIC_DRAW);

At this point, if I understand things, I have an allocated vao, and three allocated AND POPULATED vbos. I haven’t told the drawing engine anything about how to use any of this data. I just have three completely opaque buffers of random data and a vao. So if I called DrawXXX() it wouldn’t know what data to send down to the shaders, or in what order. It doesn’t see any of the data in the VBOs as xyz/color/index information yet.

Is that right?

There’s a function named glEnableClientState which DOES take arguments that look like how to use the data, like GL_COLOR_ARRAY, and GL_INDEX_ARRAY. These seem to be coupled with calls like glColorPointer and glIndexPointer which look more promising still, although I see that glIndexPointer has to do with an index into a COLOR table rather than an index into the vertex list. I note that glVertexAttribPointer is a member of glXXXPointer, and that glEnableClientState() can take GL_VERTEX_ARRAY)

Am I on the right track with glEnableClientState/glXXXPointer? If so, what is the meaning of “slot” for glVertexAttribPointer? It always seems to be zero in the examples.

What IS the next bit (after the code above) I have to do before glDrawXXX so that the drawing engine knows what to do with these fine buffers I’ve made?

Thank you.
-Jeff

While the specification contains all of the information you need, it is dense. Here’s another tip:

OpenGL features evolve from extensions. Which are all listed in the registry right next to the specifications. Take a look at ARB_vertex_buffer_object, which describes the API similarly to the core specification. But also includes an Overview justifying the functionality, Issues brought up during the design of the API (answering the “why is it this way” questions, or just of historical interest), and Usage Examples showing minimal setup to draw with buffer objects.

Similar story for vertex array objects and other APIs; the extensions that features were promoted from are all listed in Appendices at the end of the specification.

You’re almost there.

First, some history and terminology. Once again, how this works has evolved over the years and over different OpenGL versions.

Way back at the dawn of time there was what we’ll call “legacy” or “fixed-function” vertex attributes. In OpenGL 1.0 these were specified by making glColor, glNormal, glTexCoord calls, then you would make a glVertex call which would specify the position and complete the vertex. OpenGL had a concept of the “current” value of each of these attributes, so you’d have the current colour, current normal, current texcoord, and calling glColor/glNormal/glTexCoord would just store this current value and then return. glVertex would then take the set of current values, take the specified position, assemble a completed vertex from the lot, and submit to the driver for the next stage of the pipeline.

In OpenGL material you’ll often see the word “vertex” being used where what is really meant is “position”, and this is the reason why. Unfortunately you’ll sometimes have to infer from context which is actually intended. I’ll try to use “position” wherever possible just so that I’m clear, but sometimes I’ll use “vertex” when referring to parts of the OpenGL API where it’s relevant. If I do so I’ll likewise try to be clear about what I actually mean.

So with these fixed-function attributes, each vertex attribute had a specific purpose: colour, normal, texcoord, position, etc.

When vertex arrays were originally introduced in OpenGL 1.1 (I’ll ignore the earlier extension because nobody uses it) they took up a similar model and terminology, but allowed you to source your vertex data from large arrays in memory instead of specifying one attribute at a time. That’s what glEnableClientState does: it provides a way of telling OpenGL “source this attribute from an array instead of specifying it directly”. So (and this is relevant to your question):

    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_NORMAL_ARRAY);
    glEnableClientState (GL_COLOR_ARRAY);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);

Here we tell OpenGL to source all of our attributes from arrays. Now let’s declare some arrays for them; let’s say we have 200 vertices:

    float positions[200 * 3];
    float normals[200 * 3];
    unsigned char colors[200 * 4];
    float texcoords[200 * 2];

And finally we set up the actual pointers to these arrays (note that we haven’t even started using VBOs yet; that will come soon):

    glVertexPointer (3, GL_FLOAT, 0, positions);
    glNormalPointer (GL_FLOAT, 0, normals);
    glColorPointer (4, GL_UNSIGNED_BYTE, 0, colors);
    glTexCoordPointer (2, GL_FLOAT, 0, texcoords);

That’s one way of doing it where each attribute is in it’s own array. Another way is to do what’s called “interleaving”, and it will look like this:

    struct Vertex {
        float position[3];
        float normal[3];
        unsigned char color[4];
        float texcoord[2];
    };

    Vertex vertices[200];

    glVertexPointer (3, GL_FLOAT, sizeof (Vertex), vertices->position);
    glNormalPointer (GL_FLOAT, sizeof (Vertex), vertices->normal);
    glColorPointer (4, GL_UNSIGNED_BYTE, sizeof (Vertex), vertices->color);
    glTexCoordPointer (2, GL_FLOAT, sizeof (Vertex), vertices->texcoord);

Something else you might have noticed: the stride parameter in the first example is 0, which has a specific meaning in OpenGL: the attribute data is tightly packed, such that, for the example of position, it’s laid out as XYZXYZXYZ.

So now you’ve got an answer to your question for the non-VBO case and all that we need to do is look at the VBO case. Let’s just focus on the first example (separate arrays, which will translate to separate VBOs) and see how it’s different. First of all let’s put those arrays into VBOs instead:

    glBindBuffer (GL_ARRAY_BUFFER, positionsVBO);
    glBufferData (GL_ARRAY_BUFFER, sizeof (positions), positions, GL_STATIC_DRAW);

    glBindBuffer (GL_ARRAY_BUFFER, normalsVBO);
    glBufferData (GL_ARRAY_BUFFER, sizeof (normals), normals, GL_STATIC_DRAW);

    glBindBuffer (GL_ARRAY_BUFFER, colorsVBO);
    glBufferData (GL_ARRAY_BUFFER, sizeof (colors), colors, GL_STATIC_DRAW);

    glBindBuffer (GL_ARRAY_BUFFER, texcoordsVBO);
    glBufferData (GL_ARRAY_BUFFER, sizeof (texcoords), texcoords, GL_STATIC_DRAW);

Then let’s set up the pointers for them to use those VBOs:

    glBindBuffer (GL_ARRAY_BUFFER, positionsVBO);
    glVertexPointer (3, GL_FLOAT, 0, 0);

    glBindBuffer (GL_ARRAY_BUFFER, normalsVBO);
    glNormalPointer (GL_FLOAT, 0, 0);

    glBindBuffer (GL_ARRAY_BUFFER, colorsVBO);
    glColorPointer (4, GL_UNSIGNED_BYTE, 0, 0);

    glBindBuffer (GL_ARRAY_BUFFER, texcoordsVBO);
    glTexCoordPointer (2, GL_FLOAT, 0, 0);

You see how it works? The glPointer call sources it’s data from the previously bound VBO, but once you make the glPointer call that association is made and you’re free to change the bound VBO without breaking the association. This is the part that tells the driver how to use those buffers.

So the only remaining thing is how glEnableVertexAttribArray and glVertexAttribPointer fit in. You’ll remember that I mentioned “legacy” or “fixed-function” vertex attributes above? glEnableVertexAttribArray and glVertexAttribPointer provide what are instead known as “generic” vertex attributes. In other words the vertex attributes no longer have a fixed meaning, and you are free to interpret them however you please in your own shader code. The slot numbers have no significant meaning (aside from one case which I’ll come to); just pick a number (usually from 0 to 15) and so long as you’re consistent in using that same number everywhere, everything will work. By convention position is usually stored in slot 0, which is why you might typically see it in tutorials, but there’s no reason why you can’t store position in slot 7, normal in slot 8, texcoords in slot 12 and use none of the others. Otherwise the API works as I describe for legacy attributes.

The one exception for slot numbers is if you decide to mix-and-match legacy attributes with generic attributes; then you need to be careful about which slot numbers you use, but the general advice is to not do that, so it will go much easier on you if you just don’t.

Finally, forget index arrays (and glIndexPointer) exist: they’re related to running OpenGL in color-index mode, have nothing to do with indexing or index buffers, and nobody uses them any more. Wipe them from your brain and salt the earth afterwards.

Thank you very much mhagain. I sent you a PM, so check your messages.

-Jef

Hi Mhagain:

I’m still confused.

Can you show us a simple vertex shader that’s accessing position, color, normal and texture from different VBOs?

  Thanks
  Larry

The vertex shader is the same for both cases.

Use glVertexAttribPointer and glEnableVertexAttribArray accordingly. Their index parameter should match the attribute location of the shaders.

Thanks Mhagain and Silence:

That makes sense when I think about it.