Riven
« League of Dukes » JGO Kernel      Posts: 5869 Medals: 255
Hand over your head.
|
 |
«
on:
2011-05-23 15:01:59 » |
|
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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 loopOnce 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static void renderLoop() { while (!Display.isCloseRequested()) { preRender();
render();
Display.update();
Display.sync(10 ); }
Display.destroy(); } |
Clearing the screenBefore the render anything, we need to start with a clean slate by clearing the framebuffer and resetting the the OpenGL matrices. 1 2 3 4 5 6 7 8 9 10
| 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 modeNow lets start simple, by drawing a triangle using the immediate mode of OpenGL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 ArraysWe can draw exactly the same triangle using Vertex Arrays, like this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static void drawVertexArray() { 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, 3 << 2, cBuffer); glVertexPointer(3, 3 << 2, vBuffer); glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); } |
Vertex Buffer ObjectsWith 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| static void drawVertexBufferObject() { 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, 3 << 2, 0L);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cHandle); glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuffer, GL_STATIC_DRAW_ARB); glColorPointer(3, GL_FLOAT, 3 << 2, 0L);
glDrawArrays(GL_TRIANGLES, 0, 3 );
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| static void drawVertexBufferObjectIndexed() { 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();
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, 3 << 2, 0L);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, cHandle); glBufferDataARB(GL_ARRAY_BUFFER_ARB, cBuffer, GL_STATIC_DRAW_ARB); glColorPointer(3, GL_FLOAT, 3 << 2, 0L);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, iHandle); glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, iBuffer, GL_STATIC_DRAW_ARB);
glDrawElements(GL_TRIANGLES, 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);
ib.put(0, vHandle); ib.put(1, cHandle); ib.put(2, cHandle); glDeleteBuffersARB(ib); } |
Vertex Buffer Objects (interleaved)Until now we have created separate VBOs for vertices and colors. We can interleave them in two ways: 1. VCVCVC (V stands for vertex element, C stands for color element) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| static void drawVertexBufferObjectInterleaved1() { FloatBuffer vcBuffer = BufferUtils.createFloatBuffer(9 + 9);
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); vcBuffer.put(1).put(0).put(0); vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); vcBuffer.put(0).put(1).put(0); vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); vcBuffer.put(0).put(0).put(1); 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, (3 * 2) << 2, 0 << 2); glColorPointer(3, GL_FLOAT, (3 * 2) << 2, (3*1) << 2); glDrawArrays(GL_TRIANGLES, 0, 3 );
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
ib.put(0, vcHandle); glDeleteBuffersARB(ib); } |
2. VVVCCC (V stands for vertex element, C stands for color element) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| static void drawVertexBufferObjectInterleaved2() { FloatBuffer vcBuffer = BufferUtils.createFloatBuffer(9 + 9);
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); vcBuffer.put(1).put(0).put(0); vcBuffer.put(0).put(1).put(0); vcBuffer.put(0).put(0).put(1); 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, (3 * 1) << 2, 0 << 2); glColorPointer(3, GL_FLOAT, (3 * 1) << 2, (3 * 3) << 2); glDrawArrays(GL_TRIANGLES, 0, 3 );
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
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 ObjectWe 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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);
FloatBuffer vcBuffer = dataBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
vcBuffer.put(-0.5f).put(-0.5f).put(0.0f); vcBuffer.put(1).put(0).put(0); vcBuffer.put(+0.5f).put(-0.5f).put(0.0f); vcBuffer.put(0).put(1).put(0); vcBuffer.put(+0.5f).put(+0.5f).put(0.0f); vcBuffer.put(0).put(0).put(1); vcBuffer.flip();
glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); }
glVertexPointer(3, GL_FLOAT, (3 * 2) << 2, 0L << 2); glColorPointer(3, GL_FLOAT, (3 * 2) << 2, (3 * 1) << 2); glDrawArrays(GL_TRIANGLES, 0, 3 );
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
ib.put(0, vcHandle); glDeleteBuffersARB(ib); } |
Done!Finally we can render our triangle, using the 7 strategies mentioned above: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| static void render() { glClearColor(1, 1, 1, 1);
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);
drawVertexBufferObjectMapped(); } |
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.
|