Vertex Buffer Objects
Whilst vertex arrays removed the large number of function calls
that were required per vertex under immediate mode, the problem
remained that large amounts of vertex data had to be sent from
the program to the OpenGL implementation each frame. If this data
consisted entirely of static models (as is the case for many 3D
applications), removing this repeated transmission of data would
increase efficiency by orders of magnitude. The API already had
functions that facilitated one-time
bulk uploads of data: The glTexImage
family, promoted from the EXT_texture_object
extension in OpenGL 1.1. The programmer performs
the following steps in order to upload a texture to OpenGL:
Later in the program, when actually drawing primitives, the programmer
once again calls glBindTexture() to
select a texture and then supplies texture coordinates to draw textured
polygons. Note that at this point, the actual texture bitmap data could
have been discarded by the program and may only exist in the OpenGL
implementation's memory (most likely hardware texture memory).
In OpenGL 1.5,
Vertex Buffer Objects (VBOs) were
added, allowing vertex data to be uploaded to the OpenGL implementation
and then possibly discarded from the program's memory space.
Somewhat confusingly, the designers reused much of the
Vertex Array
API but added explicitly bound "buffers" in a similar manner to the
texture API. As an example
(
source):
#define GL_GLEXT_PROTOTYPES 1
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static GLuint vertices_name;
static GLuint triangles[2];
static void
init(void)
{
vertex *data = malloc(4 * sizeof(vertex));
if (data == NULL) abort();
data[0].position[0] = 0.0;
data[0].position[1] = 0.0;
data[0].position[2] = 0.0;
data[0].colour[0] = 1.0;
data[0].colour[1] = 0.0;
data[0].colour[2] = 0.0;
data[1].position[0] = 100.0;
data[1].position[1] = 0.0;
data[1].position[2] = 0.0;
data[1].colour[0] = 0.0;
data[1].colour[1] = 1.0;
data[1].colour[2] = 0.0;
data[2].position[0] = 100.0;
data[2].position[1] = 100.0;
data[2].position[2] = 0.0;
data[2].colour[0] = 0.0;
data[2].colour[1] = 0.0;
data[2].colour[2] = 1.0;
data[3].position[0] = 0.0;
data[3].position[1] = 100.0;
data[3].position[2] = 0.0;
data[3].colour[0] = 1.0;
data[3].colour[1] = 1.0;
data[3].colour[2] = 0.0;
glGenBuffers(1, &vertices_name);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), data, GL_STATIC_DRAW);
free(data);
glGenBuffers(2, triangles);
{
unsigned char indices[] = { 0, 1, 2 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
{
unsigned char indices[] = { 1, 2, 3 };
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
assert(glGetError() == GL_NO_ERROR);
}
static void
reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, -1.0, 100.0);
assert(glGetError() == GL_NO_ERROR);
}
static void
display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glTranslated(20.0, 20.0, 0.0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);
glTranslated(120.0, 120.0, 0.0);
glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix();
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Vertex array triangle");
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
Note that the code is almost identical to that of the
vertex array code, except that vertex and index data is uploaded
once and referenced later via explicitly bound buffers. The data
exists only in the OpenGL implementation's memory. Also, calls to
glVertexPointer(),
glColorPointer(),
and glDrawElements() are now passed
integer byte offsets as opposed to memory addresses. Yes, unfortunately,
this is a type error in languages with stronger type systems!
The vertex buffer API also provides functions for modifying
and/or replacing already buffered data such as
glBufferSubData() (analogous to
glTexSubImage() in the texture
API) and mapping buffers directly into the application's address
space for real-time modification
(glMapBuffer(), which is
somewhat analogous to the POSIX mmap()
function).