Sending Matrices to OpenGL
Sending matrix data to OpenGL can be confusing, but it doesn’t have to be.
The confusion arises due to misunderstanding two things: row vector / column vector and rowmajor / columnmajor.
Row Vector / Column Vector
Let’s look at a 4x4 matrix (0indexed to be programmerfriendly):
\( \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \)There are two ways to multiply a vector by this matrix. You can multiply a row vector on the left:
\( \begin{bmatrix} x & y & z &w \end{bmatrix} \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \)In which case the resultant vector is:
\( \begin{bmatrix} xm_{00} + ym_{10} + zm_{20} + wm_{30} & xm_{01} + ym_{11} + zm_{21} + wm_{31} & xm_{02} + ym_{12} + zm_{22} + wm_{32} & xm_{03} + ym_{13} + zm_{23} + wm_{33} \end{bmatrix} \)Or you can multiply a column vector on the right:
\( \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} \)And you get this:
\( \begin{bmatrix} xm_{00} + ym_{01} + zm_{02} + wm_{03} \\ xm_{10} + ym_{11} + zm_{12} + wm_{13} \\ xm_{20} + ym_{21} + zm_{22} + wm_{23} \\ xm_{30} + ym_{31} + zm_{32} + wm_{33} \end{bmatrix} \)The resultant vectors differ in shape (column vs row), with the column being more compact visually, but more importantly the resultant values are very different.
So it’s vital to know whether you’re intended to multiply the matrix by row vectors on the left or column vectors on the right.
But how do you store a matrix or a vector in a computer?
RowOrder / ColumnOrder
There are two primary ways to define a 4x4 matrix in a language like C++:


In both cases you have sixteen floats laid out consecutively in memory. The second method just makes it a little easier to index.
The computer can interpret those sixteen floats in two different ways. It can assume that it goes rowbyrow, or it can assume that it goes columnbycolumn.
With rowbyrow, or rowmajor, the computer would interpret the memory for the above matrix like this:


Columnbycolumn, or columnmajor, would instead be interpreted like this:


The actual matrix hasn’t changed, it’s just a matter of how those sixteen floats are stored in memory.
The same for a vector of four floats. The computer just stores four floats but it doesn’t know whether they’re laid out in a column or in a row.
OpenGL’s Assumptions
When you send a matrix to OpenGL it assumes that the floats you send it are in columnmajor ordering, so that’s how it will load the stream of floats into its own memory. If you stored them in your program in rowmajor, and then OpenGL interpreted them as columnmajor, your matrix would effectively be transposed on the OpenGL side, because it would read your matrix rows into its matrix columns.
\( A = \begin{bmatrix} m_{00} & m_{01} & m_{02} & m_{03} \\ m_{10} & m_{11} & m_{12} & m_{13} \\ m_{20} & m_{21} & m_{22} & m_{23} \\ m_{30} & m_{31} & m_{32} & m_{33} \end{bmatrix} \, \, A^{T} = \begin{bmatrix} m_{00} & m_{10} & m_{20} & m_{30} \\ m_{01} & m_{11} & m_{21} & m_{31} \\ m_{02} & m_{12} & m_{22} & m_{32} \\ m_{03} & m_{13} & m_{23} & m_{33} \end{bmatrix} \)If you look back up above at the rowmajor and columnmajor float representations, you’ll notice that a rowmajor ordering of \(A\) is the same as a columnmajor ordering of \(A^{T}\). And equivalently, a columnmajor ordering of \(A\) is the same as a rowmajor ordering of \(A^{T}\).
In other words, if OpenGL interprets your rowmajor floats as columnmajor, it’s the same as if you had sent the transpose of your matrix instead.
What To Do?
I like to keep my matrices stored in rowmajor order and treat my vectors as column vectors. That way multiplying a matrix and a vector will access the matrix elements in a cachefriendly pattern (rowbyrow).
A side benefit of using column vectors is that almost all linear algebra textbooks use column vectors, so it makes it easy to reference and use the textbook matrices without needing to transpose them.
Because I store them in rowmajor order and OpenGL interprets them as columnmajor, my matrices will effectively be transposed on the OpenGL side. There are two ways to counteract that.
Option 1) I can tell OpenGL to transpose my matrix before storing it (the third argument of glUniformMatrix4fv), which will put them back into the order that I expect. Then in the shader I can multiply the matrices by column vectors like I originally intended.
On the C++ side:


On the shader side:


Option 2) Or I can send my matrix to OpenGL asis, and then in the shaders just assume that my vectors are row vectors instead.
On the C++ side:


On the shader side:


The end result is identical. The product of the matrix \(A\) and a column vector is the same as the product of a row vector and the transposed matrix \(A^{T}\).
\( A \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} = \begin{bmatrix} x & y & z & w \end{bmatrix} A^{T} \)Example
As an example, let’s send the following the model matrix to OpenGL for use in the vertex shader.
\( \begin{bmatrix} 5 & 0 & 0 & 2 \\ 0 & 5 & 0 & 2 \\ 0 & 0 & 5 & 2 \\ 0 & 0 & 0 & 1 \end{bmatrix} \)We’ll assume we’re using column vectors, so we multiply a vector on the right, meaning that the vector will be uniformly scaled by 5 and translated in each direction by 2. The multiplication would look like this:
\( \begin{bmatrix} 5 & 0 & 0 & 2 \\ 0 & 5 & 0 & 2 \\ 0 & 0 & 5 & 2 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} 5x + 2 \\ 5y + 2 \\ 5z + 2 \\ 1 \end{bmatrix} \)We’ll store the matrix in rowmajor form, so the floats in memory will look like this:


If we tell OpenGL to transpose the matrix when it receives it, then the matrix on the OpenGL side will be stored as we expect, and in the vertex shader we can multiply as we would with column vectors:


However, if we don’t tell OpenGL to transpose the matrix, then it will be stored transposed on the OpenGL side, so in the vertex shader we can instead multiply as we would with row vectors:


Conclusion
The key takeaway here is that you only need to keep in mind two things: how are you storing your floats in memory on the application side, and how do you want OpenGL to store them on its side. If you store as columnmajor, then OpenGL will store them in the same way. If you store as rowmajor, then OpenGL will store them transposed. At that point you must multiply your vectors as either row vectors or column vectors, depending on your situation.
Last Edited: Dec 20, 2022