Part Eight: The Translation Matrix

The first matrix we’re going to look at, apart from the identity matrix that you’ve already seen, is the translation matrix. The translation matrix contains the distance that the X, Y, and Z coordinates should be moved, or translated, in OpenGL space.

The format of the translation matrix looks like this.

 

 

The x, y, and z positions will hold the translation values for the X, Y, and Z coordinates, respectively.

What we’re going to need to do is create a matrix, set it to identity, put in the x and y translation values, and pass it into the vertex shader. The vertex shader, in turn, will simply multiply the matrix against the current vertex, and the current vertex will be translated to the new position.

Before we start, there are a few things you need to know about matrices, and the most important of which is that matrix multiplication is not commutative. In other words, if you multiply matrix 1 by matrix 2, you may get a different result than if you had multiplied matrix 2 by matrix 1. In the multiplication of real numbers like 3 and 7, it doesn’t matter which comes first, because 3 x 7 = 21 and so does 7 x 3, but changing the order of the matrices that you multiply may change the result. This becomes very important when you start multiplying a lot of matrices together to make your model-view-projection matrix in more complicated OpenGL programs.

Another important bit of information is that when you code your matrices in your application program code, it will be in column-major order. As C programmers, we tend to think of our arrays in row-major order, meaning that we would read a matrix from left to right, then go to the next row and continue reading until we got to the bottom.

 

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

 

In C, you’d imagine that array to look like the following.

 

myArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

 

But if you give that array to OpenGL, it will look at it in column-major order, meaning that to OpenGL, this C array actually equates to the following matrix.

 

1  5  9 13
2  6 10 14
3  7 11 15
4  8 12 16

 

If you ever find the OpenGL rendering steps doing bizarre things with your models, double check your matrices to make sure you’re representing them in column-major order.

So let’s look at the translation matrix format again.

 

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

 

It’s basically an identity matrix with the x, y, and z translation variables stored in positions 13, 14, and 15 (remember, column-major order). In C, the code to populate these positions would look like this.

 

matrix[12] = transX;
matrix[13] = transY;
matrix[14] = transZ;

 

Also remember, arrays start with zero in C, so matrix[12] is the thirteenth position in the array, matrix[0] is the first position, and matrix[15] is the sixteenth and last position in the array.

Now we will get rid of those transY and transX uniform variables and replace them with a proper matrix, greatly simplifying our vertex shader code at the same time. For now, we are going to manage the matrix directly in the drawFrame method logic, but for the next part we will create a special class for matrix handling, so don’t worry if the code seems a bit ugly.

// Uniform index.
enum {
    //UNIFORM_TRANSLATE_Y, // ED: Removed
    //UNIFORM_TRANSLATE_X, // ED: Removed
    UNIFORM_MVP_MATRIX, // ED: Added
    NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];

The first thing we’re going to do is get rid of the two individual x and y translation variables and replace them with a single matrix uniform.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    // Replace the implementation of this method to do your own custom drawing.
    static const GLfloat squareVertices[] = {
        -0.5f, -0.33f,
        0.5f, -0.33f,
        -0.5f,  0.33f,
        0.5f,  0.33f,
    };
   
    static const GLubyte squareColors[] = {
        255, 255,   0, 255,
        0,   255, 255, 255,
        0,     0,   0,   0,
        255,   0, 255, 255,
    };
   
    static float transY = 0.0f;
    static float transX = 0.0f;
   
    static GLfloat mvpMatrix[16]; // ED Added
   
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
   
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
        // Use shader program.
        glUseProgram(program);
       
        /* ED: Added to initialize with identity matrix */
        // Column 1
        mvpMatrix[0] = 1;
        mvpMatrix[1] = 0;
        mvpMatrix[2] = 0;
        mvpMatrix[3] = 0;
       
        // Column 2
        mvpMatrix[4] = 0;
        mvpMatrix[5] = 1;
        mvpMatrix[6] = 0;
        mvpMatrix[7] = 0;
       
        // Column 3
        mvpMatrix[8] = 0;
        mvpMatrix[9] = 0;
        mvpMatrix[10] = 1;
        mvpMatrix[11] = 0;
       
        // Column 4
        mvpMatrix[12] = 0;
        mvpMatrix[13] = 0;
        mvpMatrix[14] = 0;
        mvpMatrix[15] = 1;
        /* ED: End identity matrix initialization */
       
        mvpMatrix[12] = cosf(transX) / 2.0f; // ED: Added, X coord translation
        mvpMatrix[13] = sinf(transY) / 2.0f; // ED: Added, Y coord translation
        mvpMatrix[14] = 0; // ED: Added, Z coord translation
       
        // Update uniform value.
        glUniformMatrix4fv(uniforms[UNIFORM_MVP_MATRIX], 1, 0, mvpMatrix); // ED: Added
        //glUniform1f(uniforms[UNIFORM_TRANSLATE_Y], (GLfloat)transY); // ED: Removed
        transY += 0.075f;  
        //glUniform1f(uniforms[UNIFORM_TRANSLATE_X], (GLfloat)transX); // ED: Added
        transX += 0.075f;
       
        // Update attribute values.
        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
        glEnableVertexAttribArray(ATTRIB_VERTEX);
        glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, squareColors);
        glEnableVertexAttribArray(ATTRIB_COLOR);
       
        // Validate program before drawing. This is a good check, but only really necessary in a debug build.
        // DEBUG macro must be defined in your debug configurations if that's not already the case.
#if defined(DEBUG)
        if (![self validateProgram:program]) {
            NSLog(@"Failed to validate program: %d", program);
            return;
        }
#endif
    }
   
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   
    [(EAGLView *)self.view presentFramebuffer];
}

This is the entire modified drawFrame method, but let’s break our changes down.

    static float transY = 0.0f;
    static float transX = 0.0f;
   
    GLfloat mvpMatrix[16]; // ED Added

After the existing transY and transX variables, we added mvpMatrix, a sixteen member array of type GLfloat. GLfloat is nothing more than a float type as defined in the OpenGL headers. OpenGL maintains its own types for cross platform compatibility. The mvpMatrix variable doesn’t need to be static, since we’ll be wiping out whatever was in it before with the next bit of code.

        /* ED: Added to initialize with identity matrix */
        // Column 1
        mvpMatrix[0] = 1;
        mvpMatrix[1] = 0;
        mvpMatrix[2] = 0;
        mvpMatrix[3] = 0;
       
        // Column 2
        mvpMatrix[4] = 0;
        mvpMatrix[5] = 1;
        mvpMatrix[6] = 0;
        mvpMatrix[7] = 0;
       
        // Column 3
        mvpMatrix[8] = 0;
        mvpMatrix[9] = 0;
        mvpMatrix[10] = 1;
        mvpMatrix[11] = 0;
       
        // Column 4
        mvpMatrix[12] = 0;
        mvpMatrix[13] = 0;
        mvpMatrix[14] = 0;
        mvpMatrix[15] = 1;
        /* ED: End identity matrix initialization */

Here, we’re loading our mvpMatrix array with an identity matrix. To make visualizing the column-major order of the array, I’ve broken the array element assignments into column chunks as OpenGL will be reading it.

In the next piece of code, we’re adding values to the translation positions of the matrix. The Z coordinate translation is zero, and doesn’t actually need to be assigned since loading the identity matrix already made this position zero, but I added it just for reference.

        mvpMatrix[12] = cosf(transX) / 2.0f; // ED: Added, X coord translation
        mvpMatrix[13] = sinf(transY) / 2.0f; // ED: Added, Y coord translation
        mvpMatrix[14] = 0; // ED: Added, Z coord translation

Notice that we are now performing the cosine and sine operation here and putting the results into the matrix.

        // Update uniform value.
        glUniformMatrix4fv(uniforms[UNIFORM_MVP_MATRIX], 1, 0, mvpMatrix); // ED: Added
        //glUniform1f(uniforms[UNIFORM_TRANSLATE_Y], (GLfloat)transY); // ED: Removed
        transY += 0.075f;  
        //glUniform1f(uniforms[UNIFORM_TRANSLATE_X], (GLfloat)transX); // ED: Added
        transX += 0.075f;

Since the old x and y translation variables are no longer in use, we’ve removed their glUniform1f() function calls and replaced them with a single glUniformMatrix4fv() function call.

The glUniformMatrix4fv() function expects a 4×4 matrix of float values, and takes the uniform name as the first parameter, which is the value we get after linking the program after compiling the shaders. The second parameter is the number of matrices being passed in, and the third parameter is a flag indicating whether or not OpenGL should transpose this matrix. The final parameter is a pointer to the matrix (or array of matrices if more than one is being passed in).

What does ‘transpose this matrix’ mean? OpenGL is expecting this array of values to be in column-major order, that is, we read the values down the first column first, then come back up to the top and read the next column down, etc. If these values happen to be in row-major order for some reason, we can still pass it into OpenGL if we specify GL_TRUE (or 1) for the transpose flag. OpenGL will transpose the matrix from row-major order to column-major order.

We also make sure that we add the new glUniformMatrix4fv() function call before we increment the transY and transX variable values.

The only other change in this file is in the loadShaders method.

- (BOOL)loadShaders
{
    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
   
    // Create shader program.
    program = glCreateProgram();
   
    // Create and compile vertex shader.
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname])
    {
        NSLog(@"Failed to compile vertex shader");
        return FALSE;
    }
   
    // Create and compile fragment shader.
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname])
    {
        NSLog(@"Failed to compile fragment shader");
        return FALSE;
    }
   
    // Attach vertex shader to program.
    glAttachShader(program, vertShader);
   
    // Attach fragment shader to program.
    glAttachShader(program, fragShader);
   
    // Bind attribute locations.
    // This needs to be done prior to linking.
    glBindAttribLocation(program, ATTRIB_VERTEX, "position");
    glBindAttribLocation(program, ATTRIB_COLOR, "color");
   
    // Link program.
    if (![self linkProgram:program])
    {
        NSLog(@"Failed to link program: %d", program);
       
        if (vertShader)
        {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader)
        {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program)
        {
            glDeleteProgram(program);
            program = 0;
        }
       
        return FALSE;
    }
   
    // Get uniform locations.
    //uniforms[UNIFORM_TRANSLATE_Y] = glGetUniformLocation(program, "translate_y"); // ED: Removed
    //uniforms[UNIFORM_TRANSLATE_X] = glGetUniformLocation(program, "translate_x"); // ED: Removed
    uniforms[UNIFORM_MVP_MATRIX] = glGetUniformLocation(program, "mvp_matrix"); // ED: Added
   
    // Release vertex and fragment shaders.
    if (vertShader)
        glDeleteShader(vertShader);
    if (fragShader)
        glDeleteShader(fragShader);
   
    return TRUE;
}

Near the end of the method, we remove the glGetUniformLocation() function calls for the individual translation uniforms and add one for the new matrix uniform.

The last change needs to be made in the vertex shader.

attribute vec4 position;
attribute vec4 color;

varying vec4 colorVarying;

//uniform float translate_y; // ED: Removed
//uniform float translate_x; // ED: Removed
uniform mat4 mvp_matrix; // ED: Added

void main()
{
    //gl_Position = position; // ED: Removed
    //gl_Position.y += sin(translate_y) / 2.0; // ED: Removed
    //gl_Position.x += cos(translate_x) / 2.0; // ED: Removed
   
    gl_Position = mvp_matrix * position;

    colorVarying = color;
}

We were able to clear out a lot of code in the shader thanks to the consolidated nature of the translation matrix. To apply our translations to the position, we simply multiply our translation matrix against the position and the operation is complete.

But what on earth is this line doing?

    gl_Position = mvp_matrix * position;

First, we’re going to need a crash course on multiplying matrices.

Because OpenGL sees our mvp_matrix and position arrays in column-major order, we multiply them together as column-major matrices. When you multiply a matrix with a column matrix, like we’re doing here, the column is always on the right. The result of the operation will always be a column matrix, but that’s great, because that’s what OpenGL wants in the gl_Position vector that we’re required to output.

The reason that the 4×4 matrix will always be on the left and the 4×1 (column) matrix will always be on the right is due to a rule for multiplying matrices that requires the inner dimensions of the matrices being multiplied to match. For example, with 4×4 * 4×1, the inner dimension is 4. If I try to go the other way, I get 4×1 * 4×4, so I can’t proceed with the multiplication because the inner dimension doesn’t match, 1 is not equal to 4.

To actually multiply the two matrices, we can go one of two ways, we can flip the column matrix and take the sums of the products of the column matrix elements and the columns of the first matrix.

For example, if we want to multiply.

 

 

We can flip the column matrix and place it over the 4×4 matrix.

 

 

We then multiply the column matrix elements down each column of the 4×4 matrix and add up the results to get our result column matrix.

 

 

I actually prefer a slightly different way, however, since it becomes more useful when multiplying 4×4 matrices together.

The other way is to simply lift the second matrix up and do the same multiplications to fill in the values below. So instead of flipping the column matrix and placing it over the 4×4 matrix, we lift it straight up and get this.

 

 

If you compare the two methods of matrix multiplication, you’ll see that they’re the same, and that we’re getting the sum of all the same values.

But it may be confusing if you’re not used to matrix math, so lets do this one step at a time. I have my two matrices, one 4×4 and one 4×1, that I want to multiply.

 

 

I raise the second matrix up to create a space for my result set, and multiply the entire column matrix by the top row of the 4×4 matrix and use the sum of the results to get my first value in the result column matrix.

 

 

So my first value in the result column matrix, r, is (a * x) + (e * y) + (i * z) + (m * w).

 

 

My second value in the result column matrix, s, is (b * x) + (f * y) + (j * z) + (n * w).

 

 

The third value in my result column matrix, t, is (c * x) + (g * y) + (k * z) + (o * w).

 

 

The final value in my result column matrix, u, is (d * x) + (h * y) + (l * z) + (p * w).

    gl_Position = mvp_matrix * position;

So now we can see that gl_Position will end up being a new column matrix based on the result of our mvp_matrix values multiplied against our original vertex coordinates as passed in from our squareVertices array.

    static const GLfloat squareVertices[] = {
        -0.5f, -0.33f,
        0.5f, -0.33f,
        -0.5f,  0.33f,
        0.5f,  0.33f,
    };

Let’s try a real world example with our first vertex, (-0.5f, -0.33f), and the first matrix we send in, the identity matrix. Look back up at the step by step multiplication and addition steps detailed above if you need to.

 

 

Sure enough, the identity matrix multiplied by the vertex coordinates equaled the vertex coordinates.

As transY and transX increase, the rainbow square moves counter-clockwise around the iPhone screen. By the time transX and transY are equal to 3.14159 radians (the equivalent of 180 degrees), the rainbow square should have moved to the opposite side of the screen along the X axis.

Let’s see what happens when we multiply our translation matrix with an X value of cos(3.14159) / 2, which is -0.5, and a Y value of sin(3.14159) / 2, which is 0, with our vertex matrix.

 

 

The Y coordinate remains unchanged at -0.33 because the Y translation value was 0. The X translation value, however, was -0.5, so the X coordinate was translated by -0.5, putting the new value at -1. The new vertex position for the lower left-hand corner of the rainbow square at 180 degrees is (-1, -0.33), and this is correct.

Take a few minutes to follow the math and you can see how important initializing the matrix to identity matrix values is. It simply wouldn’t work without it.

We now have a model-view-projection matrix being passed into the vertex shader. From here on out, we won’t need to modify the vertex shader again for any translations, rotations, or scaling we do in the future. They’ll all be included in one handy matrix.

Part Seven | Index | Part Nine