Matrix Multiplication

Matrix multiplication has a few important properties

The fact that matrix multiplication is not commutative coupled with the major and storage issues adds another level of confusion. The major of a matrix actually affects multipliation order. Multiplication order in games is often associated with OpenGL vs DirectX style multiplication, which like the OpenGL/DirectX comparison with majors is over simplifying the problem.

When multiplying two matrices, for example a 2x4 and a 3x1 matrix they have inner and outer dimensions. The inner dimensions are the numbers in the middle, 4 and 3. The outer dimensions are the numbers on the edges, 2 and 1.

Two matrices can only be multiplied together if their inner dimensions match. The result of a matrix multiplication will have the outer dimensions of the matrices being multiplied. For example, a 3x1 and a 1x2 matrix could be multiplied together and the resulting matrix would be a 3x2 matrix. These are some additional examples:

Multiplication order

Matrix multiplication is broken down into two categories Pre Multiplication and Post Multiplication. These names describe the order in which multiplication happens. Given matrices \(A\), \(B\) and \(C\), the result of \(M = ABC\) changes weather pre or post multipliation is used.

With pre-multiplication, multiplication is from left to right. Using the above example, \(A\) and \(B\) are multiplied together, the result is multiplied by \(C\) and stored in \(M\). In games, pre multiplication is often called Direct X style multiplication.

With post-multiplication, multiplication is from right to left. Still using the same example, \(C\) and \(B\) are multiplied together, the result is multiplied by \(A\) and stored in \(M\). In games, post multiplication is often refered to as OpenGL style.

Given \(X = AB\), the equation could be described as \(A\) is post multiplied by \(B\) or \(B\) is pre multiplied by \(A\). Both would give different results.

There is no right or wrong choice here. Matrix major, storage and multiplication order are all a matter of convention and consistency. You need to pick a convention that makes sense to you and stick to it. For me (and the code we write in this blog) that means column major matrices with column storage using column vectors (more about column vectors in Section 6. Transforming Vectors).

Theory

$$ (AB)_{ij} = \sum_{m}^{k=1} A_{ik}B_{kj} $$

To multiply two matrices, \(X\) = \(AB\), pretend that \(A\) is a row matrix and \(B\) is a column matrix. Find any element, \(X_{ij}\) in the result matrix by taking the dot product of row \(i\) from matrix \(A\) and column \(j\) from matrix \(B\). The following image demonstrates this:

Matrix Multiplication

Implementation

While it's possible to implement matrix multiplication using loops, most games implement it using inline SIMD code. Matrix multiplication is done many, many times per frame, it's vital that this function be as fast as possible. The code below is a minimally optimized reference implementation.

mat4 Mul(mat4 a, mat4 b) {
    mat4 result;

    // Column 0
    result[0] = a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3];
    result[1] = a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3];
    result[2] = a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3];
    result[3] = a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3];

    // Column 1
    result[4] = a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7];
    result[5] = a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7];
    result[6] = a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7];
    result[7] = a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7];

    // Column 2
    result[8] = a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11];
    result[9] = a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11];
    result[10] = a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11];
    result[11] = a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11];

    // Column 3
    result[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15];
    result[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15];
    result[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15];
    result[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15];

    return result;
}