A matrix is just a big grid of numbers with rules that define how we can multiply it with other grids or lists of numbers.
In games, we want to construct a matrix so that, when multiplied with a list of numbers representing a source position (say, the position of a vertex in a mesh) we get a list of numbers representing a destination position (say, the position of that vertex in the game world, after applying the transformations of all its parent objects, or the position of the vertex projected on the screen by our camera)
Because of how matrix multiplication is defined, we can either do:
Matrix * ColumnVector = RowVector
RowVector * Matrix = ColumnVector
I'm most familiar with the first convention, so that's what I'll use for the examples below. If your environment uses the opposite convention, just transpose all of the examples below (turn each column into a row)
p_destination = M * p_source
Where | px |
p_source = | py | Is the source position we want to transform.
| pz | The "w" corrdinate of 1 means we're using homogeneous coordinates
| 1 | to apply translation (setting this to 0 will only rotate/scale/skew,
└ ┘ which is desireable when transforming directions like surface normals)
A typical object transform in games (not including camera projection) will look like this:
M = | m00 m01 m02 m03 | = T * R * S
| m10 m11 m12 m13 | ie.
| m20 m21 m22 m23 | p_destination = M * p_source
| 0 0 0 1 | p_destination = T * R * S * p_source
Since we rarely deliberately introduce skew in object transformations, this matrix is generally the product of three distinct transformations, we could look at as being three distinct matrices:
T = | 1 0 0 tx | Translation by the vector (tx, ty, tz)
| 0 1 0 ty |
| 0 0 1 tz |
| 0 0 0 1 |
S = | sx 0 0 0 | Axis-aligned scale by (sx, sy, sz)
| 0 sy 0 0 |
| 0 0 sz 0 |
| 0 0 0 1 |
┌ ┐ (Given a left-handed, y-up coordinate system)
R = | rx ux fx 0 | Rotation so that:
| ry uy fy 0 | local x axis points to r = (rx, ry, rz) "right"
| rz uz fz 0 | local y axis points to u = (ux, uy, uz) "up"
| 0 0 0 1 | local z axis points to f = (fx, fy, fz) "forward"
└ ┘ Where r, u, f are all perpendicular unit vectors (a basis)
We can compute the r, u, f vectors using pitch, roll, and yaw angles.
These angles are just a compact way to represent an orientation, called "Euler Angles;"
we generally unpack them to a matrix or quaternion when transforming a lot of vectors
because the less-compact representations are more efficient to use in bulk calculations,
and can avoid snares like "gimbal lock" that creep in when modifying Euler angles.
Bringing it all together, by multiplying these three transformations:
M = | sx*rx sy*ux sz*fx tx |
| sx*ry sy*uy sz*fy ty |
| sx*rz sy*uz sz*fz tz |
| 0 0 0 1 |
Using the conventions above, you can see vectors we care about often occur as columns in the matrix (eg. translation in the 4th column, direction vectors in the first three columns of the rotation matrix). So, if we use a column-major matrix representation (where consecutive elements in a column are consecutive in storage order), this makes it convenient to set or read columns of the matrix (eg.
T = (tx, ty, tz, 1) to set the 4th column of a translation matrix). A row-major representation would store consecutive elements in a row consecutively.
So, row-major vs column-major is just about using the data representation that's most convenient for the matrix multiplication conventions used by your engine or programming environment.