Introduction to Vertex Arrays and Vertex Buffer Objects
Copy 'n Paste OpenGL
It is my suspicion that most aspiring developers these days prefer to copy snippets of code, paste them into their own project, see if it works, modify a few things, and work from there. Therefore I decided not to explain too much in this tutorial, because most is either self descriptive or properly explained elsewhere (see here).
Having said that, here goes:
Getting started…
First we initialize an OpenGL context through LWJGL:
import org.lwjgl.*;
import org.lwjgl.opengl.*;
import static org.lwjgl.opengl.ARBBufferObject.*;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;
import static org.lwjgl.opengl.GL11.*;
public class JgoVbo
{
static void initContext() throws LWJGLException
{
int w = 640;
int h = 480;
Display.setDisplayMode(new DisplayMode(w, h));
Display.setFullscreen(false);
Display.create();
glViewport(0, 0, w, h);
}
Render loop
Once we have the context, we need some loop that will prepare a frame, render some graphics and showing it on screen, until we close the window.
static void renderLoop()
{
while (!Display.isCloseRequested())
{
preRender();
render();
Display.update();
Display.sync(10 /* desired fps */);
}
Display.destroy();
}
Clearing the screen
Before the render anything, we need to start with a clean slate by clearing the framebuffer and resetting the the OpenGL matrices.
static void preRender()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
Immediate mode
Now lets start simple, by drawing a triangle using the immediate mode of OpenGL:
static void drawImmediateMode()
{
glBegin(GL_TRIANGLES);
glColor3f(1, 0, 0);
glVertex3f(-0.5f, -0.5f, 0.0f);
glColor3f(0, 1, 0);
glVertex3f(+0.5f, -0.5f, 0.0f);
glColor3f(0, 0, 1);
glVertex3f(+0.5f, +0.5f, 0.0f);
glEnd();
}
Output:
Vertex Arrays
We can draw exactly the same triangle using Vertex Arrays, like this:
static void drawVertexArray()
{
// create geometry buffers
FloatBuffer cBuffer = BufferUtils.createFloatBuffer(9);
cBuffer.put(1).put(0).put(0);
cBuffer.put(0).put(1).put(0);
cBuffer.put(0).put(0).put(1);
cBuffer.flip();
FloatBuffer vBuffer = BufferUtils.createFloatBuffer(9);
vBuffer.put(-0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(+0.5f).put(0.0f);
vBuffer.flip();
//
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3, /* stride */3 << 2, cBuffer);
glVertexPointer(3, /* stride */3 << 2, vBuffer);
glDrawArrays(GL_TRIANGLES, 0, /* elements */3);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
Vertex Buffer Objects
With Vertex Buffer Objects (VBOs) we need to first generate resources on the GPU, and access them by their handles. The idea is to keep that geometry as long as possible on the GPU, so, as opposed to Vertex Arrays, we don’t have to upload them every frame.
static void drawVertexBufferObject()
{
// create geometry buffers
FloatBuffer cBuffer = BufferUtils.createFloatBuffer(9);
cBuffer.put(1).put(0).put(0);
cBuffer.put(0).put(1).put(0);
cBuffer.put(0).put(0).put(1);
cBuffer.flip();
FloatBuffer vBuffer = BufferUtils.createFloatBuffer(9);
vBuffer.put(-0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(+0.5f).put(0.0f);
vBuffer.flip();
//
IntBuffer ib = BufferUtils.createIntBuffer(2);
glGenBuffersARB(ib);
int vHandle = ib.get(0);
int cHandle = ib.get(1);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, vBuffer, GL_STATIC_DRAW_ARB);
glVertexPointer(3, GL_FLOAT, /* stride */3 << 2, 0L);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuffer, GL_STATIC_DRAW_ARB);
glColorPointer(3, GL_FLOAT, /* stride */3 << 2, 0L);
glDrawArrays(GL_TRIANGLES, 0, 3 /* elements */);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup VBO handles
ib.put(0, vHandle);
ib.put(1, cHandle);
glDeleteBuffersARB(ib);
}
Vertex Buffer Objects (indexed)
Now we might want to render the trangle using indexed geometry. That means you have a buffer that holds the indices of the triangles we want to render. This way you can reuse geometry (vertices) for more than one triangle (shared vertices). The only difference is that we need to create an index buffer, which also is a VBO, and fill it. In most cases 16 bit indices are a good choice:
static void drawVertexBufferObjectIndexed()
{
// create geometry buffers
FloatBuffer cBuffer = BufferUtils.createFloatBuffer(9);
cBuffer.put(1).put(0).put(0);
cBuffer.put(0).put(1).put(0);
cBuffer.put(0).put(0).put(1);
cBuffer.flip();
FloatBuffer vBuffer = BufferUtils.createFloatBuffer(9);
vBuffer.put(-0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(-0.5f).put(0.0f);
vBuffer.put(+0.5f).put(+0.5f).put(0.0f);
vBuffer.flip();
// create index buffer
ShortBuffer iBuffer = BufferUtils.createShortBuffer(3);
iBuffer.put((short) 0);
iBuffer.put((short) 1);
iBuffer.put((short) 2);
iBuffer.flip();
//
IntBuffer ib = BufferUtils.createIntBuffer(3);
glGenBuffersARB(ib);
int vHandle = ib.get(0);
int cHandle = ib.get(1);
int iHandle = ib.get(2);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, vBuffer, GL_STATIC_DRAW_ARB);
glVertexPointer(3, GL_FLOAT, /* stride */3 << 2, 0L);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuffer, GL_STATIC_DRAW_ARB);
glColorPointer(3, GL_FLOAT, /* stride */3 << 2, 0L);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, iHandle);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, iBuffer, GL_STATIC_DRAW_ARB);
glDrawElements(GL_TRIANGLES, /* elements */3, GL_UNSIGNED_SHORT, 0L);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup VBO handles
ib.put(0, vHandle);
ib.put(1, cHandle);
ib.put(2, iHandle);
glDeleteBuffersARB(ib);
}
Vertex Buffer Objects (interleaved)
Until now we have created separate VBOs for vertices and colors. We can interleave them in two ways:
- VCVCVC (V stands for vertex element, C stands for color element)
static void drawVertexBufferObjectInterleaved1()
{
// create geometry buffer (both vertex and color)
FloatBuffer vcBuffer = BufferUtils.createFloatBuffer(9 + 9);
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(1).put(0).put(0); // c
vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(0).put(1).put(0); // c
vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); // v
vcBuffer.put(0).put(0).put(1); // c
vcBuffer.flip();
//
IntBuffer ib = BufferUtils.createIntBuffer(1);
glGenBuffersARB(ib);
int vcHandle = ib.get(0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vcHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, vcBuffer, GL_STATIC_DRAW_ARB);
glVertexPointer(3, GL_FLOAT, /* stride **/(3 * 2) << 2, /* offset **/0 << 2); // float at index 0
glColorPointer(3, GL_FLOAT, /* stride **/(3 * 2) << 2, /* offset **/(3*1) << 2); // float at index 3
glDrawArrays(GL_TRIANGLES, 0, 3 /* elements */);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup VBO handles
ib.put(0, vcHandle);
glDeleteBuffersARB(ib);
}
- VVVCCC (V stands for vertex element, C stands for color element)
static void drawVertexBufferObjectInterleaved2()
{
// create geometry buffer (both vertex and color)
FloatBuffer vcBuffer = BufferUtils.createFloatBuffer(9 + 9);
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); // v
vcBuffer.put(1).put(0).put(0); // c
vcBuffer.put(0).put(1).put(0); // c
vcBuffer.put(0).put(0).put(1); // c
vcBuffer.flip();
//
IntBuffer ib = BufferUtils.createIntBuffer(1);
glGenBuffersARB(ib);
int vcHandle = ib.get(0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vcHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, vcBuffer, GL_STATIC_DRAW_ARB);
glVertexPointer(3, GL_FLOAT, /* stride **/(3 * 1) << 2, /* offset */0 << 2); // float at index 0
glColorPointer(3, GL_FLOAT, /* stride **/(3 * 1) << 2, /* offset */(3 * 3) << 2); // float at index 9
glDrawArrays(GL_TRIANGLES, 0, 3 /* elements */);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup VBO handles
ib.put(0, vcHandle);
glDeleteBuffersARB(ib);
}
Pay close attention to the values specified as stride
and offset
in both examples of interleaved rendering.
Mapped Vertex Buffer Object
We can also request a memory region from the driver, to push our vertex data into. We obtain this memory region as a ByteBuffer, in which we can solely write, using the glMapBuffer(…). When we’ve defined the data, we call glUnmapBuffer(…) to notify the driver we are done, allowing the driver exclusive access to the data.
static void drawVertexBufferObjectInterleavedMapped()
{
IntBuffer ib = BufferUtils.createIntBuffer(1);
glGenBuffersARB(ib);
int vcHandle = ib.get(0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vcHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, (9 + 9) << 2, GL_STATIC_DRAW_ARB);
{
ByteBuffer dataBuffer = glMapBufferARB(GL_ARRAY_BUFFER_ARB, ARBBufferObject.GL_WRITE_ONLY_ARB, (9 + 9) << 2, null);
// create geometry buffer (both vertex and color)
FloatBuffer vcBuffer = dataBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(1).put(0).put(0); // c
vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); // v
vcBuffer.put(0).put(1).put(0); // c
vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); // v
vcBuffer.put(0).put(0).put(1); // c
vcBuffer.flip();
glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
}
glVertexPointer(3, GL_FLOAT, /* stride */(3 * 2) << 2, /* offset */0L << 2); // float at index 0
glColorPointer(3, GL_FLOAT, /* stride */(3 * 2) << 2, /* offset */(3 * 1) << 2); // float at index 3
glDrawArrays(GL_TRIANGLES, 0, 3 /* elements */);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup VBO handles
ib.put(0, vcHandle);
glDeleteBuffersARB(ib);
}
Done!
Finally we can render our triangle, using the 7 strategies mentioned above:
static void render()
{
glClearColor(1, 1, 1, 1);
// start drawing a triangle with the different strategies
drawImmediateMode();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexArray();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexBufferObject();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexBufferObjectIndexed();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexBufferObjectInterleaved1();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexBufferObjectInterleaved2();
glTranslatef(+0.05f, -0.05f, 0);
drawVertexBufferObjectInterleavedMapped();
}
To prevent the triangles to be rendered on top of eachother, a slight offset/translation is added after drawing each triangle.
Output:
Although I agree the output is far from entertaining, it should help you implementing your own geometry rendering strategy.