Lazy Foo' Productions


GLM Matrices

GLM Matrices screenshot

Last Updated: Oct 19th, 2014

In OpenGL 3.0+, the built in OpenGL matrix functions from OpenGL 2.1 are deprecated. Since modern OpenGL implementations want you to roll your own matrix functionality, we'll be using the GL math library to use our own custom matrix operations for our GLSL shader.

Setting up GLM


If you know linear algebra (and you should if you're reading this tutorial), you could create your own matrix classes in an afternoon. Since we're strapped for time and we don't want to spend effort on reinventing the wheel, we'll be using the tried and tested open source library GL Math. You can download the library here.

Unlike freeGLUT or even freetype, GLM is a header only library. To set it up, just extract the archive and make sure your compiler/IDE is looking at the directory from the archive.
From LPlainPolygonProgram2D.glvs
//Transformation Matrices
uniform mat4 LProjectionMatrix;
uniform mat4 LModelViewMatrix;

void main()
{
    //Process vertex
    gl_Position = LProjectionMatrix * LModelViewMatrix * gl_Vertex;
}
As you can see in our updated vertex shader, we have our own custom 4x4 matrices replacing the old built in ones. Glancing at the main function, you'll see they function the same as the old ones did.
From LPlainPolygonProgram2D.h
#include "LShaderProgram.h"
#include <glm/glm.hpp>

class LPlainPolygonProgram2D : public LShaderProgram
{
    public:
        LPlainPolygonProgram2D();
        /*
        Pre Condition:
         -None
        Post Condition:
         -Initializes variables
        Side Effects:
         -None
        */

        bool loadProgram();
        /*
        Pre Condition:
         -A valid OpenGL context
        Post Condition:
         -Loads plain polygon program
        Side Effects:
         -None
        */

        void setColor( GLfloat r, GLfloat g, GLfloat b, GLfloat a = 1.f );
        /*
        Pre Condition:
         -Bound LPlainPolygonProgram2D
        Post Condition:
         -Updates uniform color used for rendering
        Side Effects:
         -None
        */

        void setProjection( glm::mat4 matrix );
        /*
        Pre Condition:
         -None
        Post Condition:
         -Sets member projection matrix
        Side Effects:
         -None
        */

        void setModelView( glm::mat4 matrix );
        /*
        Pre Condition:
         -None
        Post Condition:
         -Sets member modelview matrix
        Side Effects:
         -None
        */

        void leftMultProjection( glm::mat4 matrix );
        /*
        Pre Condition:
         -None
        Post Condition:
         -Left multiplies member projection matrix
        Side Effects:
         -None
        */

        void leftMultModelView( glm::mat4 matrix );
        /*
        Pre Condition:
         -None
        Post Condition:
         -Left multiplies member modelview matrix
        Side Effects:
         -None
        */

        void updateProjection();
        /*
        Pre Condition:
         -Bound LPlainPolygonProgram2D
        Post Condition:
         -Updates shader program projection matrix with member projection matrix
        Side Effects:
         -None
        */

        void updateModelView();
        /*
        Pre Condition:
         -Bound LPlainPolygonProgram2D
        Post Condition:
         -Updates shader program modelview matrix with member modelview matrix
        Side Effects:
         -None
        */
At the top of the LPlainPolygonProgram2D class header we include the glm library header. We also have a bunch of new functions for the new matrices we added to the shader program. We'll go over those in the cpp file.
From LPlainPolygonProgram2D.h
    private:
        //Color uniform location
        GLint mPolygonColorLocation;

        //Projection matrix
        glm::mat4 mProjectionMatrix;
        GLint mProjectionMatrixLocation;

        //Modelview matrix
        glm::mat4 mModelViewMatrix;
        GLint mModelViewMatrixLocation;
};
Here's the new variables added for the new matrix functionality. The mat4 data type from the glm library is the 4x4 matrix we'll be using the replace the old fixed function matrices. We have one for the projection and modelview matrix. We also have location variables for each of the matrices so we can update them in the GLSL shader program.
From LPlainPolygonProgram2D.cpp
#include "LPlainPolygonProgram2D.h"
#include <glm/gtc/type_ptr.hpp>

LPlainPolygonProgram2D::LPlainPolygonProgram2D()
{
    mPolygonColorLocation = 0;
    mProjectionMatrixLocation = 0;
    mModelViewMatrixLocation = 0;
}
At the top of LPlainPolygonProgram2D.cpp we include the type pointer header from GLM so we'll be able to get the data pointer from the mat4 matrices to update them in the GLSL shader.

In the updated constructor for the LPlainPolygonProgram2D class we initialize the location variables. If you're wondering what the mat4 objects initialize to, the default constructor for the class has them initialize to the identity matrix.
From LPlainPolygonProgram2D.cpp
bool LPlainPolygonProgram2D::loadProgram()
{
    //Generate program
    mProgramID = glCreateProgram();

    //Load vertex shader
    GLuint vertexShader = loadShaderFromFile( "32_glm_matrices/LPlainPolygonProgram2D.glvs", GL_VERTEX_SHADER );

    //Check for errors
    if( vertexShader == 0 )
    {
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Attach vertex shader to program
    glAttachShader( mProgramID, vertexShader );


    //Create fragment shader
    GLuint fragmentShader = loadShaderFromFile( "32_glm_matrices/LPlainPolygonProgram2D.glfs", GL_FRAGMENT_SHADER );

    //Check for errors
    if( fragmentShader == 0 )
    {
        glDeleteShader( vertexShader );
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Attach fragment shader to program
    glAttachShader( mProgramID, fragmentShader );

    //Link program
    glLinkProgram( mProgramID );

    //Check for errors
    GLint programSuccess = GL_TRUE;
    glGetProgramiv( mProgramID, GL_LINK_STATUS, &programSuccess );
    if( programSuccess != GL_TRUE )
    {
        printf( "Error linking program %d!\n", mProgramID );
        printProgramLog( mProgramID );
        glDeleteShader( vertexShader );
        glDeleteShader( fragmentShader );
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Clean up excess shader references
    glDeleteShader( vertexShader );
    glDeleteShader( fragmentShader );

    //Get variable locations
    mPolygonColorLocation = glGetUniformLocation( mProgramID, "LPolygonColor" );
    if( mPolygonColorLocation == -1 )
    {
        printf( "%s is not a valid glsl program variable!\n", "LPolygonColor" );
    }

    mProjectionMatrixLocation = glGetUniformLocation( mProgramID, "LProjectionMatrix" );
    if( mProjectionMatrixLocation == -1 )
    {
        printf( "%s is not a valid glsl program variable!\n", "LProjectionMatrix" );
    }

    mModelViewMatrixLocation = glGetUniformLocation( mProgramID, "LModelViewMatrix" );
    if( mModelViewMatrixLocation == -1 )
    {
        printf( "%s is not a valid glsl program variable!\n", "LModelViewMatrix" );
    }

    return true;
}
The loading function for this shader is pretty much the same as before, only now it gets the locations for the new uniform variables.
From LPlainPolygonProgram2D.cpp
void LPlainPolygonProgram2D::setProjection( glm::mat4 matrix )
{
    mProjectionMatrix = matrix;
}

void LPlainPolygonProgram2D::setModelView( glm::mat4 matrix )
{
    mModelViewMatrix = matrix;
}

void LPlainPolygonProgram2D::leftMultProjection( glm::mat4 matrix )
{
    mProjectionMatrix = matrix * mProjectionMatrix;
}

void LPlainPolygonProgram2D::leftMultModelView( glm::mat4 matrix )
{
    mModelViewMatrix = matrix * mModelViewMatrix;
}
Here are the functions to assign values to or left multiply the projection/modelview matrices for our shader program.
From LPlainPolygonProgram2D.cpp
void LPlainPolygonProgram2D::updateProjection()
{
    glUniformMatrix4fv( mProjectionMatrixLocation, 1, GL_FALSE, glm::value_ptr( mProjectionMatrix ) );
}

void LPlainPolygonProgram2D::updateModelView()
{
    glUniformMatrix4fv( mModelViewMatrixLocation, 1, GL_FALSE, glm::value_ptr( mModelViewMatrix ) );
}
When you're done with your matrix operations and you want to start rendering, we have these functions that update the matrix variables in the shader. Matrix uniforms have the special function glUniformMatrix4() to update them. The first argument is the variable location, the second is how many matrices we're going to be updating, the third is whether we want to transpose the matrix data we're sending and the fourth argument is the pointer to the 16 GLfloat array that represents the matrix.

If you want to roll your own matrix library, know that glUniformMatrix4() accepts the matrix array in column major order:
00 04 08 12
01 05 09 13
02 06 10 14
03 07 11 15
You may be wondering why we don't just call glUniformMatrix4() after each matrix operation. It doesn't really make sense to send each operation to the GPU if we're only going to use the final form of the matrix to multiply against vertex data. The rule of thumb is do all the matrix operation client side and when you're ready to process vertex data update the uniform in the shader.
From LUtil.cpp
#include "LUtil.h"
#include <IL/il.h>
#include <IL/ilu.h>
#include "LPlainPolygonProgram2D.h"
#include "LVertexPos2D.h"
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>

//Basic shader
LPlainPolygonProgram2D gPlainPolygonProgram2D;

//VBO names
GLuint gVBO = NULL;
GLuint gIBO = NULL;
At the top of LUtil.cpp we include the transformation header from GLM. GLM has functionality built in to replace the old OpenGL matrix operation which we'll be using in a little bit.
From LUtil.cpp
bool initGL()
{
    //Initialize GLEW
    GLenum glewError = glewInit();
    if( glewError != GLEW_OK )
    {
        printf( "Error initializing GLEW! %s\n", glewGetErrorString( glewError ) );
        return false;
    }

    //Make sure OpenGL 2.1 is supported
    if( !GLEW_VERSION_2_1 )
    {
        printf( "OpenGL 2.1 not supported!\n" );
        return false;
    }

    //Set the viewport
    glViewport( 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT );

    //Initialize clear color
    glClearColor( 0.f, 0.f, 0.f, 1.f );

    //Enable texturing
    glEnable( GL_TEXTURE_2D );

    //Set blending
    glEnable( GL_BLEND );
    glDisable( GL_DEPTH_TEST );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

    //Check for error
    GLenum error = glGetError();
    if( error != GL_NO_ERROR )
    {
        printf( "Error initializing OpenGL! %s\n", gluErrorString( error ) );
        return false;
    }

    //Initialize DevIL and DevILU
    ilInit();
    iluInit();
    ilClearColour( 255, 255, 255, 000 );

    //Check for error
    ILenum ilError = ilGetError();
    if( ilError != IL_NO_ERROR )
    {
        printf( "Error initializing DevIL! %s\n", iluErrorString( ilError ) );
        return false;
    }

    return true;
}
Since we're no longer using the built in matrices from the OpenGL 2.1 days, we removed the deprecated matrix function calls from our initGL() function.
From LUtil.cpp
bool loadGP()
{
    //Load basic shader program
    if( !gPlainPolygonProgram2D.loadProgram() )
    {
        printf( "Unable to load basic shader!\n" );
        return false;
    }

    //Bind basic shader program
    gPlainPolygonProgram2D.bind();

    //Initialize projection
    gPlainPolygonProgram2D.setProjection( glm::ortho<GLfloat>( 0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 1.0, -1.0 ) );
    gPlainPolygonProgram2D.updateProjection();

    //Initialize modelview
    gPlainPolygonProgram2D.setModelView( glm::mat4() );
    gPlainPolygonProgram2D.updateModelView();

    return true;
}
In the loadGP() function we load and bind our shader program as usual. Then we set the projection matrix in our shader program using the ortho() function in the GLM library. This function returns a mat4 matrix that's the same as the orthgraphic matrix used by the old glOrtho() function call. We then update the projection matrix in the shader program.

Then we set the modelview matrix. Since the mat4 class constructs the identity matrix by default, passing in a mat4 object will set the modelview matrix to the identity matrix.
From LUtil.cpp
void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );

    //Solid cyan quad in the center
    gPlainPolygonProgram2D.setModelView( glm::translate<GLfloat>( glm::vec3( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ) ) );
    gPlainPolygonProgram2D.updateModelView();
    gPlainPolygonProgram2D.setColor( 0.f, 1.f, 1.f );
When we render, we want to translate the quad to the center of the screen. Fortunately, the GLM library also replaced the glTranslate() function. All we have to do is pass in the x/y/z translation as a vector.

Next we update the modelview matrix and set the rendering color in the shader program.
From LUtil.cpp
    //Enable vertex arrays
    glEnableClientState( GL_VERTEX_ARRAY );

        //Set vertex data
        glBindBuffer( GL_ARRAY_BUFFER, gVBO );
        glVertexPointer( 2, GL_FLOAT, 0, NULL );

        //Draw quad using vertex data and index data
        glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gIBO );
        glDrawElements( GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL );

    //Disable vertex arrays
    glDisableClientState( GL_VERTEX_ARRAY );

    //Update screen
    glutSwapBuffers();
}
Finally here we render the quad. There's just one problem: GL_QUAD primitives are also deprecated in OpenGL 3.0+. Fortunately we can use triangle fans to render a quad as two triangles like this:
triangle fan

You can look up more on how triangle fans work in the OpenGL documentation. For now, just know they're a quick way to replace the old GL_QUADs.