Vertex Arrays
Vertex arrays first appeared in OpenGL 1.1
as a means to more efficiently specify vertex data. The programmer
allocates an array in ordinary system memory (using standard C
malloc(), for example), fills it
with vertex data and then calls one function to draw vertices using
that array as the data source. As an example
(
source):
The
glEnableClientState(GL_VERTEX_ARRAY)
call enables the use of client-side vertex arrays. The
glVertexPointer(3, GL_FLOAT, 0, vertices)
call tells OpenGL that the array referenced by
vertices contains three-element
vertices of type GL_FLOAT, with
0 bytes between each successive
element. The glDrawArrays(GL_TRIANGLES, 0, 3)
call asks OpenGL to draw three vertices, starting at offset
0.
The API allows the programmer quite a lot of freedom in terms of how
the data is arranged in order to be passed to OpenGL. The following
example shows the storing of vertex coordinates and colour values
in two separate arrays
(
source):
The above example uses separate arrays for each vertex attribute.
For better cache locality, it is preferable to store one array
of a record type and then tell OpenGL how to retrieve the values
it needs from this single array. An example of this is as follows
(
source):
#include <assert.h>
#include <stddef.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
/*
* Interleaved vertex and colour data.
*/
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static vertex data0[] = {
{ { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } },
{ { 100.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } },
{ { 100.0, 100.0, 0.0 }, { 0.0, 0.0, 1.0 } }
};
/*
* Vertex data followed by colour data.
*/
static vector3 data1[] = {
{ 0.0, 0.0, 0.0 },
{ 100.0, 0.0, 0.0 },
{ 100.0, 100.0, 0.0 },
{ 1.0, 0.0, 0.0 },
{ 0.0, 1.0, 0.0 },
{ 0.0, 0.0, 1.0 }
};
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);
{
const unsigned char *data0_v = (unsigned char *) &data0;
const unsigned char *data0_c = ((unsigned char *) &data0) + offsetof(vertex, colour);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data0_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data0_c);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
glTranslated(120.0, 120.0, 0.0);
{
const unsigned char *data1_v = (unsigned char *) &data1;
const unsigned char *data1_c = ((unsigned char *) &data1) + (3 * sizeof(vector3));
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(vector3), data1_v);
glColorPointer(3, GL_FLOAT, sizeof(vector3), data1_c);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
}
glPopMatrix();
assert(glGetError() == GL_NO_ERROR);
glutSwapBuffers();
}
int
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutCreateWindow("Vertex array triangle");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
Each glVertexPointer and
glColorPointer call specifies
a "stride" value and an initial offset into the array. The "stride"
value specifies the number of bytes that need to be skipped in order
to retrieve the next value of a given type from the array.
As the illustration above shows: To read colour values, start
at &data0 + offsetof(vertex, colour),
read 3 values of type
GL_FLOAT and then skip
sizeof(vertex) bytes. Reading
vertex position values is the same, except that the initial
offset is 0 (reading directly
from the start of the array). Obviously, an infinite number
of permutations are possible. It is also possible to interleave
other data into the array using functions such as
glNormalPointer,
glTexCoordPointer, etc.
It is also possible to specify an array of indices to
be used for rendering, as opposed to having
glDrawArrays simply walk through
the current arrays
(
source):
#include <assert.h>
#include <stddef.h>
#include <GL/glut.h>
#include <GL/glext.h>
typedef float vector3[3];
typedef struct
{
vector3 position;
vector3 colour;
} vertex;
static vertex data[] = {
{ { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } },
{ { 100.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } },
{ { 100.0, 100.0, 0.0 }, { 0.0, 0.0, 1.0 } },
{ { 0.0, 100.0, 0.0 }, { 1.0, 1.0, 0.0 } },
};
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);
{
const unsigned char *data_v = (unsigned char *) &data;
const unsigned char *data_c = ((unsigned char *) &data) + offsetof(vertex, colour);
{
const unsigned char indices[] = { 0, 1, 2 };
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data_c);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, indices);
}
glTranslated(120.0, 120.0, 0.0);
{
const unsigned char indices[] = { 1, 2, 3 };
glVertexPointer(3, GL_FLOAT, sizeof(vertex), data_v);
glColorPointer(3, GL_FLOAT, sizeof(vertex), data_c);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, indices);
}
}
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");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutIdleFunc(glutPostRedisplay);
glutMainLoop();
return 0;
}
The only new code is the use of
glDrawElements, which takes
an array of indices. Note that four vertices are declared in the
vertex array and then glDrawElements
is used to draw one triangle from vertices
{0, 1, 2} and another
triangle from vertices {1, 2, 3}.