io7m | single-page | multi-page | archive (zip, signature)
3. Display ListsA Brief History Of Vertex Specification In OpenGL 5. Vertex Buffer Objects
PreviousUpNext

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):
#include <assert.h>
#include <GL/glut.h>
#include <GL/glext.h>

typedef float vector3[3];

static vector3 vertices[] = {
  { 0.0,   0.0,   0.0 },
  { 100.0, 0.0,   0.0 },
  { 100.0, 100.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);
  glVertexPointer(3, GL_FLOAT, 0, vertices);
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableClientState(GL_VERTEX_ARRAY);
  glPopMatrix();

  glutSwapBuffers();
}

int
main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutCreateWindow("Vertex array triangle");
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutIdleFunc(glutPostRedisplay);

  glutMainLoop();
  return 0;
}
      
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):
#include <assert.h>
#include <GL/glut.h>
#include <GL/glext.h>

typedef float vector3[3];

static vector3 vertices[] = {
  { 0.0,   0.0,   0.0 },
  { 100.0, 0.0,   0.0 },
  { 100.0, 100.0, 0.0 }
};
static vector3 colors[] = {
  { 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);

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);

  glVertexPointer(3, GL_FLOAT, 0, vertices);
  glColorPointer(3, GL_FLOAT, 0, colors);
  glDrawArrays(GL_TRIANGLES, 0, 3);

  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 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}.

PreviousUpNext
3. Display ListsA Brief History Of Vertex Specification In OpenGL 5. Vertex Buffer Objects