Overview
This worksheet addresses the single most common source of matrix bugs in OpenGL code: the difference between how a matrix looks on paper and how GLM stores it in memory. You will read GLM matrix memory correctly, explain why multiplication is written M * v and not v * M, and write GLM function calls in the right order to produce a desired transform sequence.
Learning Objectives
By the end of this lab you will be able to:
- By the end of this worksheet you will be able to state what column-major storage means and show how a translation matrix is laid out in GLM memory
- By the end of this worksheet you will be able to explain why matrix-vector multiplication is written M * v in GLM, not v * M
- By the end of this worksheet you will be able to identify a wrong GLM call order from its on-screen symptom and write the correct version
- By the end of this worksheet you will be able to write GLM function calls in the order that produces a specified sequence of transforms
Concept
A student writes a translation matrix on paper with the offset in the top-right corner, looks at the raw memory from GLM, and sees the offset at a completely different position. They assume the matrix is transposed and start adding calls to glm::transpose. The object still moves in the wrong direction. This misunderstanding causes more assignment bugs in this module than any other single topic.
Row-major storage places the first row of a matrix in memory first: elements [0..3] are row 0, [4..7] are row 1, and so on. Column-major storage (used by OpenGL and GLM) places the first column in memory first: elements [0..3] are column 0, [4..7] are column 1, and so on. The same mathematical matrix looks transposed when you read the raw memory as if it were row-major. The matrix itself is not transposed. Only the storage order differs. In GLM, the correct indexing is mat[col][row], not mat[row][col].
Multiplication order follows from the column-vector convention: the matrix goes on the left, the column vector on the right — M * v. GLM function calls left-multiply the accumulated matrix. Calling model = glm::rotate(model, angle, axis) gives model = R * model. A call written later in the source applies to the geometry before a call written earlier. Write GLM calls in the reverse of the mathematical order you want the transforms applied.
Tasks
Task 1 - Row-Major vs Column-Major Side by Side
Use the translation matrix T(3, 1, 0) throughout this task.
How it looks on paper (row-major reading — each row displayed left to right):
Math notation: | 1 0 0 3 | | 0 1 0 1 | | 0 0 1 0 | | 0 0 0 1 |
How GLM stores it in memory (column-major — first column occupies indices 0-3, second column 4-7, etc.):
GLM memory (16 floats, column by column):
Index: [ 0][ 1][ 2][ 3] [ 4][ 5][ 6][ 7] [ 8][ 9][10][11] [12][13][14][15]
Value: 1 0 0 0 0 1 0 0 0 0 1 0 3 1 0 1
---- col 0 ---- ---- col 1 ---- ---- col 2 ---- ---- col 3 ----
Key observation: the translation values (3, 1, 0) appear at memory indices 12, 13, and 14. In column-major layout, column 3 (the rightmost column of the matrix as written on paper) is stored last in memory.
A common mistake: reading the raw memory row by row and concluding the matrix is transposed. It is not transposed. The storage order simply differs from the reading convention. If you access mat[0][3] in GLM you get the translation value 3 — because GLM uses mat[col][row] indexing, and column 0, row 3 is the bottom-left entry of the matrix, which is 0 for a translation matrix. The translation is at mat[3][0], mat[3][1], and mat[3][2] (column 3, rows 0-2).
In GLM, the correct indexing order is mat[][row], not mat[row][col].
For T(3, 1, 0), the translation value 3 is stored at GLM memory index .
The translation values in a 4x4 matrix occupy the column (0-indexed) in GLM memory.
In column-major storage, elements [0..3] in memory represent column of the matrix.
Task 2 - Wrong Multiplication Order
A student wants a cube to spin on its own axis while positioned at (5, 0, 0) in the scene. The desired behaviour: the cube rotates around its own local Y axis without moving from x = 5. The student writes the following code. Study the symptom, then answer the questions below.
Observed symptom: instead of spinning in place at x = 5, the cube swings in a wide arc around the world origin, tracing a circle on the XZ plane.
glm::mat4 model(1.0f);
model = glm::translate(model, glm::vec3(5.0f, 0.0f, 0.0f)); // line A
model = glm::rotate(model, angle, glm::vec3(0.0f, 1.0f, 0.0f)); // line B
Which line causes the wrong behaviour, and why does it produce the orbiting symptom instead of spinning in place?
Write the corrected two-line GLM code that makes the cube spin on its own axis at position (5, 0, 0).
Task 3 - Write the GLM Calls in the Right Order
Desired transform (described in words): translate the object to (0, 2, 0), then rotate it 30 degrees around the Z axis. The desired mathematical matrix is T(0, 2, 0) * R_z(30).
- Write the two GLM calls in the correct order to achieve T * R_z (remember: GLM calls are written in reverse of the mathematical application order).
- Trace the vertex v = (1, 0, 0, 1) through the transform by hand.
cos(30) = sqrt(3)/2 (approx 0.866), sin(30) = 0.5.
R_z(30) * (1, 0, 0, 1) = (cos30, sin30, 0, 1) = (0.866, 0.5, 0, 1)
Then apply T(0, 2, 0): add (0, 2, 0) to the position.
Write the two GLM calls in the correct order to produce T(0, 2, 0) * R_z(30). State which call goes first and why.
Trace v = (1, 0, 0, 1) through T(0,2,0) * R_z(30) step by step. What is the final position?
Solution
Try the tasks above before revealing the solution.