Recently, I have been getting a lot of similar questions about how to draw geometry in OpenGL without the use of glBegin()/glEnd(). This is mostly due to the interest in iPhone development which uses OpenGL ES 1.1, though I have received a few desktop performance questions as well. Since I've gotten multiple questions, I thought I would post a very simple tutorial for VBOs.
When most people first learn OpenGL, they are taught using glBegin() and glEnd(). But to shock of many people, these functions have been excluded from OpenGL ES, and there is pressure to remove these functions from future versions of OpenGL proper. The two main reasons for removing these functions are performance and simplicity.
In performance, the glBegin()/glEnd() technique is very slow. The first problem is coined 'immediate mode' drawing. In immediate mode drawing, the system is essentially dispatching all drawing commands on demand from the main system (which is CPU+system RAM+system bus) to the graphics card (GPU). In current modern architectures, both GPUs and CPUs are extremely powerful, but main system memory and the system bus are comparatively slow and create huge bottlenecks when trying to send data between them. Hence drawing in immediate mode is slow because you are waiting to send large amounts of data through the system bus to the GPU.
In addition, glBegin()/glEnd() style code creates a lot of function call overhead because you call a separate function for every vertex, color, texture coordinate, and normal. This overhead is non-trivial for objects with a lot of data.
With regards to simplicity, there are many ways to draw in OpenGL that have been added through the years. Having so many redundant ways to draw is confusing. In addition, OpenGL driver writers have a much bigger job because they have to support many more things. Typically driver writers try to optimize everything they can, but optimizing glBegin()/glEnd() is actually tricky because there are so many different permutations available to describe objects. For example:
glBegin(GL_TRIANGLES); glColor(); glNormal(); glTexCoord(); glVertex(); glColor(); glNormal(); glTexCoord(); glVertex(); glColor(); glNormal(); glTexCoord(); glVertex(); glEnd();
glBegin(GL_TRIANGLES); glColor(); glNormal(); glTexCoord(); glVertex(); glTexCoord(); glNormal(); glVertex(); glTexCoord(); glVertex();
In the latter form, I basically say I want to apply a single color to everything. I also shuffle the order to the glTexCoord() and glNormal() which shouldn't change the drawing result. And in the last line, I omit the call to glNormal which I then expect to mean continue using the same value as the previous.
// Initialization: glGenBuffers(); // create a buffer object glBindBuffer(); // use the buffer glBufferData(); // allocate memory in the buffer <stuff to get memory into the buffer> glVertexPointer(); // tell OpenGL how your data is packed inside the buffer
// Draw: glBindBuffer(); // use the buffer glEnableClientState(); // enable the parts of the data you want to draw glDrawArrays(); // the actual draw command
// allocate a new buffer glGenBuffers(1, &cubeVBO); // bind the buffer object to use glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
const GLsizeiptr vertex_size = NUMBER_OF_CUBE_VERTICES*NUMBER_OF_CUBE_COMPONENTS_PER_VERTEX*sizeof(GLfloat); const GLsizeiptr color_size = NUMBER_OF_CUBE_COLORS*NUMBER_OF_CUBE_COMPONENTS_PER_COLOR*sizeof(GLubyte); // allocate enough space for the VBO
glBufferData(GL_ARRAY_BUFFER, vertex_size+color_size, 0, GL_STATIC_DRAW);
GLvoid* vbo_buffer = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES); // transfer the vertex data to the VBO memcpy(vbo_buffer, s_cubeVertices, vertex_size); // append color data to vertex data. To be optimal, // data should probably be interleaved and not appended vbo_buffer += vertex_size; memcpy(vbo_buffer, s_cubeColors, color_size); glUnmapBufferOES(GL_ARRAY_BUFFER);
// Describe to OpenGL where the vertex data is in the buffer glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)((char*)NULL)); // Describe to OpenGL where the color data is in the buffer glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)((char*)NULL+vertex_size));
// create index buffer glGenBuffers(1, &cubeIBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIBO); // For constrast, instead of glBufferSubData and glMapBuffer, // we can directly supply the data in one-shot glBufferData(GL_ELEMENT_ARRAY_BUFFER, NUMBER_OF_CUBE_INDICES*sizeof(GLubyte), s_cubeIndices, GL_STATIC_DRAW);
// Activate the VBOs to draw glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIBO); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); // This is the actual draw command glDrawElements(GL_TRIANGLE_STRIP, NUMBER_OF_CUBE_INDICES, GL_UNSIGNED_BYTE, (GLvoid*)((char*)NULL));
glDeleteBuffers(1, &cubeIBO); glDeleteBuffers(1, &cubeVBO);