Lazy Foo' Productions


Vertex Arrays

Vertex Arrays screenshot

Last Updated: Nov 22nd, 2012

When ever you make an OpenGL call, it gets put on the OpenGL command buffer. When you're dealing with models that have tons of polygons, it's wasteful to have a call to glVertex() for each individual vertex. This is one of the reasons the function glVertex() was deprecated in OpenGL 3. Vertex arrays allow us to send vertex data in sets instead of individually.
From LVertexPos2D.h
#ifndef LVERTEX_POS_2D_H
#define LVERTEX_POS_2D_H

#include "LOpenGL.h"

struct LVertexPos2D
{
    GLfloat x;
    GLfloat y;
};

#endif
We have a new file for our project for a new data type. LVertexPos2D defines a 2D vertex position. Having this data type will make it easier for us to define geometry.
From LUtil.cpp
#include "LUtil.h"
#include <IL/il.h>
#include <IL/ilu.h>
#include "LVertexPos2D.h"

//Quad vertices
LVertexPos2D gQuadVertices[ 4 ];
At the top of LUtil.cpp, we declare the 4 vertex positions for our quad.
From LUtil.cpp
bool loadMedia()
{
    //Set quad verticies
    gQuadVertices[ 0 ].x = SCREEN_WIDTH * 1.f / 4.f;
    gQuadVertices[ 0 ].y = SCREEN_HEIGHT * 1.f / 4.f;

    gQuadVertices[ 1 ].x = SCREEN_WIDTH * 3.f / 4.f;
    gQuadVertices[ 1 ].y = SCREEN_HEIGHT * 1.f / 4.f;

    gQuadVertices[ 2 ].x = SCREEN_WIDTH * 3.f / 4.f;
    gQuadVertices[ 2 ].y = SCREEN_HEIGHT * 3.f / 4.f;

    gQuadVertices[ 3 ].x = SCREEN_WIDTH * 1.f / 4.f;
    gQuadVertices[ 3 ].y = SCREEN_HEIGHT * 3.f / 4.f;

    return true;
}
In loadMedia() we don't actually load any files, but we do define the vertices for our quad.
From LUtil.cpp
void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );

    //Enable vertex arrays
    glEnableClientState( GL_VERTEX_ARRAY );

        //Set vertex data
        glVertexPointer( 2, GL_FLOAT, 0, gQuadVertices );
At the top of render(), we clear our screen as usual. Then we call glEnableClientState() to enable vertex arrays so we can start sending our vertex data.

Then we call glVertexPointer() to define our vertex data. The first argument is how many coordinates per vertex there are, which is two in this case since we're doing 2D. The second argument is the data type for each coordinate, which in this case we're using GLfloat. The third argument is stride, which the byte offset between vertices. We'll use this in future tutorials, but for now we're setting it to zero.

The last argument is the pointer to our array of floats. You may be thinking "We're giving it a pointer to an array of LVertexPos2D, not floats". Actually, if we want to we can treat a LVertexPos2D like an array of floats.
Sample code
LVertexPos2D position;
GLfloat* components = (GLfloat*)&position;
components[ 0 ];//First member of the struct (x)
components[ 1 ];//Second member of the struct (y)
A struct is just a list of different variables which can be of different types. LVertexPos2D is a list of 2 GLfloats, so we can treat as an array of 2 GLfloats. Since we have an array of 4 LVertexPos2D, we can treat the data like an array of 8 GLfloats.

There are exceptions to treating a struct of floats like an array of floats. Some processor architectures can load arrays of structs faster if they are a certain size. Compilers for these architectures will then pad each element in the array to be a certain size. This means in between each structure of floats there will be a few stray bytes. In this case, you need to set the stride to be the sizeof(LVertexPos2D). So far, every compiler/processor combination I've used doesn't pad arrays of structs. In case your vertex data seems garbeled try to see if your compiler pads the elements in your array.
From LUtil.cpp
        //Draw quad using vertex data
        glDrawArrays( GL_QUADS, 0, 4 );

    //Disable vertex arrays
    glDisableClientState( GL_VERTEX_ARRAY );

    //Update screen
    glutSwapBuffers();
}
Now that the GPU has the address of our vertex data, we call glDrawArrays() to tell it to draw with the vertex data we sent it. The first argument is what type of geometry we want to draw with the vertex data. The second argument is the index in the array of vertices you want to start drawing with, in this case it's 0 since we want to start drawing from the beginning of the vertex data. The last argument is how many vertices you want to draw which in this case is obviously 4 since we want to draw a single quad. After we're done drawing with our vertex data, we disable the sending of vertex data with glDisableClientState() after we're done rendering.

Now the only thing that's changed here is how the vertex data is sent. The vertices themselves still functions the same way. Try making calls to glRotate() or some other tranformation before drawing your vertex array and it will function the same as if you sent each vertex one by one using glVertex().