Part Nine: The Scale Matrix

The scale matrix is straightforward, similar to the translation matrix. There is an x value position, a y value position, and a z value position.

 

 

Unlike the translation values, however, the scale values are right along the diagonal line that the identity matrix was using. If you look carefully at the last matrix multiplication example in the previous chapter, you’ll notice that those strategically placed ones along the diagonal are what preserved our values from the vertex matrix we were multiplying against. By replacing these with scaling values, we will be applying the scale factors directly to the vertex matrix as we multiply.

For example, if I wanted my square to be half as big as it is now, I would apply a scaling factor of 0.5 (one half) to the x and y positions.

Let’s work through a complete example. We know that our square currently has the following vertices.

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

We also know it’s drawn like this.

 

 

So the width is 1 full unit (-0.5 to 0.5), and the height is 0.66 units (-0.33 to 0.33) to account for the iPhone’s screen aspect ratio.

Ignoring the translation matrix for a moment, what happens if I use my model-view-projection matrix to apply a scale of 0.5 to the x and y values?

 

 

The first vertex, (-0.5, -0.33), is scaled to (-0.25, -0.165). That looks right, let’s keep going.

 

 

The second vertex, (0.5, -0.33), is scaled to (0.25, -0.165). This is looking pretty good, let’s quickly get the last two vertices and see if everything worked properly.

 

 

The third vertex, (-0.5, 0.33), is scaled to (-0.25, 0.165).

 

 

The final vertex, (0.5, 0.33), is scaled to (0.25, 0.165).

Our vertex array going into the rendering process was (-0.5, -0.33), (0.5, -0.33), (-0.5, 0.33), and (0.5, 0.33). That’s a square one unit wide and 0.66 units high.

After applying our scale matrix to all of the points, our vertex array becomes (-0.25, -0.165), (0.25, -0.165), (-0.25, 0.165), and (0.25, 0.165). That’s a square 0.5 units wide (-0.25 to 0.25) and 0.33 units high (-0.165 to 0.165). Looks like the scale operation worked.

To apply scaling to our example program, we’re going to simply throw the scale values into the matrix we’re using for translations to see what happens.

The only change in our code that we need to make is the following.

- (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;
   
    GLfloat mvpMatrix[16];
   
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
   
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
        // Use shader program.
        glUseProgram(program);
       
        // 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;
       
        mvpMatrix[12] = cosf(transX) / 2.0f;
        mvpMatrix[13] = sinf(transY) / 2.0f;
        mvpMatrix[14] = 0;
       
        //ED: Added x and y scale values of 0.5
        mvpMatrix[0] = 0.5f; // X scale value position
        mvpMatrix[5] = 0.5f; // Y scale value position
        mvpMatrix[10] = 1; // Z scale position
       
        // Update uniform value.
        glUniformMatrix4fv(uniforms[UNIFORM_MVP_MATRIX], 1, 0, mvpMatrix);
        transY += 0.075f;  
        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];
}

In the drawFrame method, right after the translation values are added, we throw in X, Y, and Z scale values. Once again, the Z value is only there to demonstrate where and how a Z scale would be valued, but our square doesn’t use the Z coordinate, and that position is already a one anyway from the identity matrix.

It also doesn’t matter whether I threw in the scale values before or after the translation values, since the values occupy different positions in the same matrix. The only important thing is that I apply the identity matrix first, and then value any matrix positions.

Now that we have a matrix with both translation and scaling values populated, let’s run the program and see what it does.

 

iPhone screen

 

Great! The square not only still travels around the screen in a counter-clockwise circle, it’s also been scaled to half its original size.

This code, as great as it is, is starting to look pretty messy. Not only that, but I prefer to keep separate matrices for things like translations and scaling, and I don’t want to put another huge block of code in to declare and initialize a second matrix.

What we need is a utility class for handling all of this matrix work for us, so we’re going to build one in the next chapter.

Part Eight | Index | Part Ten