Edit: Before going to my original reply below, lets talk briefly about the Hello example.
- I would count "Hello" as a single object / draw call. What I call object can have unique transformation and can get culled individually.
- You can draw it with a single draw call using indexed primitives using glDrawElemenst(GL_TRIANGLES, ...)
- Each letter has 4 vertices and one quad = two triangles = 6 indices
- Every time you add a new letter to the text, you append 4 vertices to VBO and 6 indices to the index buffer
Vertices for two letters ("Hi" for example)
Those would use indices 0, 1, 2, 0, 2, 3, /* H */ 4, 5, 6, 4, 6, 7, /* i */
| / | | / |
Since changing size of GL buffer sizes is a bad idea, text buffer is typically created with fixed size vertex and index buffers big enough to hold some reasonable maximum amount of text (say 2000 letters).
Notice that while contents of vertex buffer depend on the text, index buffer contents will always be constant. Thus you can prepare contents of index buffer when you create the text buffer, and all you need to do when appending a letter is to append the vertex data to the VBO.
- You can put multiple vertex arrays to a single VBO, interleaved or not.
This is basic feature of glVertexAttribPointer, shown below.
- You can also put multiple sets of (interleaved or not) vertex arrays into a single VBO, for multiple objects.
This is slightly more involved, as you need to tell GL which set you want to use, and there several way to do it.
Edit: This does not exactly answer the original question though..
Read the documentation for glVertexAttribPointer, a few times. This is the function that you use to tell GL exactly where each attribute gets the values from. Below is some notes that assume 2 objects, first with 10 vertices, second with 20, and vertex format with vec3 position and vec3 color, interleaved.
- You need to have the correct VBO bound before you call VertexAttribPointer
- Stride is the total size of the vertex format. For the example setup described above, stride would be 3 * 4 + 3 * 4 = 24 bytes (size of vec3 plus size of vec3).
- Offset for the first attribute (position in my example) is always 0.
- Offset for second attribute (color in my example) is offset of previous attribute + size of previous attribute + possibly additional padding required for alignment (*). In the example, offset of the color would be 0 + 12 + 0 = 12.
With those pieces of information, when you want to draw first object, you'd call
// Assuming you already have the correct VBO bound
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, 0); glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, 12); glEnableVertexAttribArray(1);
// Draw the object
If you can, you probably should use VAO and split the above to VAO setup code and draw code with VAO bind.
(*) Offsets must be aligned to the size of the type, so GL_FLOAT attributes for example must be aligned to 4 bytes. Typically people don't need padding in vertex format, but if there is any, it is included in offsets and stride.
Now for the second part: In order to draw multiple objects from a single VBO, it depends on the GL version and supported extensions how you should do it. NOTE: Below is only a subset of possibilities, newer GL has even more options..
A) Using base vertex:
- If you have GL 3.2 or GL_ARB_draw_elements_base_vertex extension, you can use glDrawElementsBaseVertex() which has an extra argument to tell the first vertex
- In the example base vertex for first object would be 0, and 10 for the second object
- You only need to do the vertex attrib pointer setup (as above, directly with vertexAttribPointer calls, or, if possible, ideally by binding VAO) when switching the VBO or vertex format.
- You can put as many objects as you want to a single VBO and no need to change attribute pointer setup, if they share same vertex format.
- Note that index values for each object start from 0.
- Each object typically have their own indices, so indices pointer (offset to IBO) passed to draw call is unique per mesh
B) Without base vertex, using baked indices:
- If you cannot use base vertex (no GL 3.2 or GL_ARB_draw_elements_base_vertex - for example, OpenGL ES), you can "bake" base vertex into the indices
- The first object would start indices from 0, the second object would start indices from 10
- Like option A, you only update attribute pointer setup when switching VBO or vertex format
- Indices for each object start from where previous object ended. This means you can easily run out of 16-bit indices, a limitation option A did not have
- Edit: You can merge multiple draw calls into single draw call, if the objects share same transform and so on. So you could possibly consider H, e, l, l, o as individual objects which share the VBO and get drawn with a single draw call.
C) Using VAO per object, object offset added to attribute offset
- Create a VAO per object, and add an "object offset" to all attribute offsets
- Object offset for first object would be 0
- Object offset for second object would be stride * number of vertices, in the example that would be 24 * 10 = 240
- You setup the VAO during startup
- At draw time you update attribute pointers by binding VAO before draw call
For the second object you would then do at startup:
// gen and bind VAO for the second object
// make sure correct VBO is bound
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, 0 + 240); glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, 12 + 240); glEnableVertexAttribArray(1);
// bind the index buffer
// Optionally: bind VAO 0 to avoid accidentally modifying this VAO ever after
At draw time:
// bind VAO for the second object - This gets all the state needed to get attribute pointers to VBO right
// draw call
D) Object offset added to vertex attribute pointers, no VAO nor base vertex available
- This is like option C, but without VAO, so you do all VertexAttribPointer setup for every object
- Not really recommended, it would be better to use option B instead.
E) One VBO per object, attrib pointer calls before every draw call
- Just for comparison
- Same as D but in addition to all vertex attrib pointer setup calls, one also binds the VBO
- The basic version, possibly most often used
- Any of the above options is probably better than this - if you try them:
- If you settle for this: