Camera Matrix and Axis Vectors

Started by
6 comments, last by D.V.D 7 years, 5 months ago

Hey guys,

I've been watching 3blue1browns video series on linear algebra (

) and I decided to try and implement matrices and vectors myself instead of blindly following tutorial code without really understanding it. I've ran into a problem with my camera matrix, specifically the rotation aspect of it. I'm working with column ordered matrices and in a Left hand coordinate system.

From the videos, he explains that matrices are just a set of new axis or basis vectors which define the new x,y,z,... axis for some vector multiplied by that matrix. As I understand it, the camera matrix (if the camera is at the origin) should transform a vector such that its x axis is the cameras horizontal vector, its y axis is the cameras up vector, and its z axis is the cameras target vector. But everywhere I look, they say that for column ordered matrices, the first column should be [Horizontal.x, Up.x, Target.x, 0], the second column should be [Horizontal.y, Up.y, Target.y, 0] and so on. The videos say that the columns of a matrix are the new axis vectors so that would mean that the camera matrix would transform some vector such that its x axis is the x component of the horizontal, up and target vectors, the y axis is the y component of the horizontal, up and target vectors, and so on.

My question is, how does that make sense? Shouldn't the new axis vectors be Horizontal, Up and Target instead?

Advertisement

If your matrix is row major then each basic would be a row. and the last row would be your translation.

If you matrix is column major then each basic would be a column and the last column would be your translation. Now in the case of the ViewMatrix, it is the Transpose of the 3x3 row or 3x3 column depending on whether you are using row major or column major because the ViewMatrix is the Transpose of your world in that case if you have a row major matrix then your right axis would be your first column. Hope it makes sense.One thing to always remember is that OpenGL/DirectX always expect element 12,13,14,15 of the matrix to be translation. However you decide to use the other elements is completely up to you, as long you remain consistent.

Oh okay, so the reason that the view matrix isn't what I think it is is because we are trying to perform the opposite rotations that apply to the camera and that happens to be the transpose of the 3x3 rotation matrix? So if the camera's view is rotated to the left by 90 the degrees, the view matrix will contain a rotation by -90 degrees instead right?

Oh okay, so the reason that the view matrix isn't what I think it is is because we are trying to perform the opposite rotations that apply to the camera and that happens to be the transpose of the 3x3 rotation matrix? So if the camera's view is rotated to the left by 90 the degrees, the view matrix will contain a rotation by -90 degrees instead right?

Yeah, the easist way that I find to create a view matrix is to construct a "local-to-world" (aka world) matrix as if the camera was an object in the world, and then invert this matrix to get a "world-to-camera" (aka view) matrix.

If a 3x3 matrix only contains the three axis, then transposing it is the same as inverting it (and cheaper).

One thing to always remember is that OpenGL/DirectX always expect element 12,13,14,15 of the matrix to be translation. However you decide to use the other elements is completely up to you, as long you remain consistent

No they don't.

You can use column-major maths, which looks on paper like:

$$\begin{bmatrix} Right.x & Up.x & Forward.x & Pos.x \\ Right.y & Up.y & Forward.y & Pos.y \\ Right.z & Up.z & Forward.z & Pos.z \\ 0 & 0 & 0 & 1 \end{bmatrix}$$

or row-major maths, which looks on paper like:

$$\begin{bmatrix} Right.x & Right.y & Right.z & 0 \\ Up.x & Up.y & Up.z & 0 \\ Forward.x & Forward.y & Forward.z & 0 \\ Pos.x & Pos.y & Pos.z & 1 \end{bmatrix}$$
And you can use column-major arrays, or row-major arrays.
If you use column-major maths with column-major arrays, or if you use row-major maths with row-major arrays, then your array of 16 floats will look like:
Right.x, Right.y, Right.z, 0, Up.x , Up.y, Up.z, 0, Forward.x, Forward.y, Forward.z, 0, Pos.x, Pos.y, Pos.z, 1
If you use column-major maths with row-major arrays, or if you use row-major maths with column-major arrays, then your array of 16 floats will look like:
Right.x, Up.x, Forward.x, Pos.x, Right.y, Up.y, Forward.y, Pos.y, Right.z, Up.z, Forward.z, Pos.z, 0, 0, 0, 1
All four of those choices of conventions are supported by D3D and OpenGL.
The mathematical convention alters how you write your math, e.g. whether you write vOut = vIn * projection * view * world, or vOut = world * view * projection * vIn.
The array convention alters how you write your matrix library, and whether you write column_major float4x4 myMatrix; or row_major float4x4 myMatrix; in your shader code.
If you're using an existing matrix library, then both of these choices may have already been made for you.

I don't know if this will help, but here is a breakdown of the math.

To transform vector p from local space into world space for a given object (defined by vectors position, right, up and look), this transformation is as follows:

p_world = object.posvector + p_local.x * object.rightvector + p_local.y * object.upvector + p_local.z * object.lookvector

expanding them, we get the following:

p_world.x = objec.posvector.x + p_local.x * object.rightvector.x + p_local.y * object.upvector.x + p_local.z * object.lookvector.x

p_world.y = objec.posvector.y + p_local.x * object.rightvector.y + p_local.y * object.upvector.y + p_local.z * object.lookvector.y

p_world.z = objec.posvector.z + p_local.x * object.rightvector.z + p_local.y * object.upvector.z + p_local.z * object.lookvector.z

Conversely, to transform vector p from world space into the objects local space (where the position, right, up and look vectors are <0,0,0>, <1,0,0>, <0,1,0> and <0,0,1> respectively):

p_local.x = ( p_world - object.posvector ) dot object.rightvector

p_local.y = ( p_world - object.posvector ) dot object.upvector

p_local.z = ( p_world - object.posvector ) dot object.lookvector

Given the distributive property of the dot product define as ( a + b ) dot c = a dot c + b dot c, we can expand this as follows:

p_local.x = p_world dot object.rightvector - object.posvector dot object.rightvector

p_local.y = p_world dot object.upvector - object.posvector dot object.upvector

p_local.z = p_world dot object.lookvector - object.posvector dot object.lookvector

As long as your matrix operations honor these transformations, you will be good. Also, I havent watched your video, but I thought i should add this in case it wasnt covered.
Matrix multiplications (vectors are essentially 1x3 and 3x1 matrices) consist of dot products between rows and columns, ALWAYS - the row on the left (bra-vectors, or 3x1 matrix) and the column on the right (ket-vectors, or 1x3 matrix). My physics professor once described it as "bracket notation", that stands for <bra | ket >. A matrix multiplication between ket vector and a bra vector < ket | bra > does not make any sense: this is the same as a 3x1 matrix multiplied by a 1x3 matrix, which would result in a 3x3 matrix.

Oh okay, so the reason that the view matrix isn't what I think it is is because we are trying to perform the opposite rotations that apply to the camera and that happens to be the transpose of the 3x3 rotation matrix? So if the camera's view is rotated to the left by 90 the degrees, the view matrix will contain a rotation by -90 degrees instead right?

Yeah, the easist way that I find to create a view matrix is to construct a "local-to-world" (aka world) matrix as if the camera was an object in the world, and then invert this matrix to get a "world-to-camera" (aka view) matrix.

If a 3x3 matrix only contains the three axis, then transposing it is the same as inverting it (and cheaper).

One thing to always remember is that OpenGL/DirectX always expect element 12,13,14,15 of the matrix to be translation. However you decide to use the other elements is completely up to you, as long you remain consistent

No they don't.

You can use column-major maths, which looks on paper like:

$$\begin{bmatrix} Right.x & Up.x & Forward.x & Pos.x \\ Right.y & Up.y & Forward.y & Pos.y \\ Right.z & Up.z & Forward.z & Pos.z \\ 0 & 0 & 0 & 1 \end{bmatrix}$$

or row-major maths, which looks on paper like:

$$\begin{bmatrix} Right.x & Right.y & Right.z & 0 \\ Up.x & Up.y & Up.z & 0 \\ Forward.x & Forward.y & Forward.z & 0 \\ Pos.x & Pos.y & Pos.z & 1 \end{bmatrix}$$
And you can use column-major arrays, or row-major arrays.
If you use column-major maths with column-major arrays, or if you use row-major maths with row-major arrays, then your array of 16 floats will look like:
Right.x, Right.y, Right.z, 0, Up.x , Up.y, Up.z, 0, Forward.x, Forward.y, Forward.z, 0, Pos.x, Pos.y, Pos.z, 1
If you use column-major maths with row-major arrays, or if you use row-major maths with column-major arrays, then your array of 16 floats will look like:
Right.x, Up.x, Forward.x, Pos.x, Right.y, Up.y, Forward.y, Pos.y, Right.z, Up.z, Forward.z, Pos.z, 0, 0, 0, 1
All four of those choices of conventions are supported by D3D and OpenGL.
The mathematical convention alters how you write your math, e.g. whether you write vOut = vIn * projection * view * world, or vOut = world * view * projection * vIn.
The array convention alters how you write your matrix library, and whether you write column_major float4x4 myMatrix; or row_major float4x4 myMatrix; in your shader code.
If you're using an existing matrix library, then both of these choices may have already been made for you.

Okay this makes sense. Just to clarify though, are the basis vectors in a row order matrix the rows or are they always columns? I found a blog post on the ryg blog that talks about matrix ordering and he says that whatever algorithm you use for matrix multiplication, it doesn't depend on the ordering of the matrices. Currently, I think of matrix ordering as, you write matrices a certain way and the columns are always the basis vectors but you can choose to store things such that rows are sequential in memory or columns are.

I don't know if this will help, but here is a breakdown of the math.

To transform vector p from local space into world space for a given object (defined by vectors position, right, up and look), this transformation is as follows:

p_world = object.posvector + p_local.x * object.rightvector + p_local.y * object.upvector + p_local.z * object.lookvector

expanding them, we get the following:

p_world.x = objec.posvector.x + p_local.x * object.rightvector.x + p_local.y * object.upvector.x + p_local.z * object.lookvector.x

p_world.y = objec.posvector.y + p_local.x * object.rightvector.y + p_local.y * object.upvector.y + p_local.z * object.lookvector.y

p_world.z = objec.posvector.z + p_local.x * object.rightvector.z + p_local.y * object.upvector.z + p_local.z * object.lookvector.z

Conversely, to transform vector p from world space into the objects local space (where the position, right, up and look vectors are <0,0,0>, <1,0,0>, <0,1,0> and <0,0,1> respectively):

p_local.x = ( p_world - object.posvector ) dot object.rightvector

p_local.y = ( p_world - object.posvector ) dot object.upvector

p_local.z = ( p_world - object.posvector ) dot object.lookvector

Given the distributive property of the dot product define as ( a + b ) dot c = a dot c + b dot c, we can expand this as follows:

p_local.x = p_world dot object.rightvector - object.posvector dot object.rightvector

p_local.y = p_world dot object.upvector - object.posvector dot object.upvector

p_local.z = p_world dot object.lookvector - object.posvector dot object.lookvector

As long as your matrix operations honor these transformations, you will be good. Also, I havent watched your video, but I thought i should add this in case it wasnt covered.
Matrix multiplications (vectors are essentially 1x3 and 3x1 matrices) consist of dot products between rows and columns, ALWAYS - the row on the left (bra-vectors, or 3x1 matrix) and the column on the right (ket-vectors, or 1x3 matrix). My physics professor once described it as "bracket notation", that stands for <bra | ket >. A matrix multiplication between ket vector and a bra vector < ket | bra > does not make any sense: this is the same as a 3x1 matrix multiplied by a 1x3 matrix, which would result in a 3x3 matrix.

This makes some sense, I'll go over it a bit to better understand it but in the videos, matrix multiplication is not explained as dot products (as it usually is in other resources) since the lectures try to explain matrices more as a change of basis vectors and as linear transformations. I know its equivalent, but the matrix multiplication formula in the videos is easier to understand but requires knowing what your basis vectors are.

If B is some matrix that A can multiply with, and B has n basis vectors, than matrix multiplication is defined as such:

A*B = A*Basis0 | A*Basis1 | ... | A*Basisn, where each of the results of A times the ith basis vector becomes the ith column of the resulting matrix. Then you can decompose matrix vector multiplication to be each component of the vector times the corresponding basis in the matrix and you add all of those results together. Basically, it makes the code become something super simple like this:



inline v4 operator*(m4 A, v4 B)
{
    v4 Result = {};
    Result = B.x*A.v[0] + B.y*A.v[1] + B.z*A.v[2] + B.w*A.v[3];

    return Result;
}

inline m4 operator*(m4 A, m4 B)
{
    m4 Result = {};

    Result.v[0] = A*B.v[0];
    Result.v[1] = A*B.v[1];
    Result.v[2] = A*B.v[2];
    Result.v[3] = A*B.v[3];
    
    return Result;
}

(v[0-3] are the columns or basis stored in the matrix). This probably isn't the most efficient code for matrix multiplication but its conceptually easy to understand and its not as complicated as other code which has a ton of inner loops and what not.

I found a blog post on the ryg blog that talks about matrix ordering and he says that whatever algorithm you use for matrix multiplication, it doesn't depend on the ordering of the matrices.
That's correct. The matrix multiplication operation does not care what is in the matrices. They might not contain basis vecotrs at all, but perhaps weighting of how much you like different ice-cream flavours. Regardless of what kind of mathematical convention you're using (whether you're writing your basis vectors horizontally or vertically), your matrix multiplication function will be implemented the same way.

However, your 1D-array-storage convention does matter. e.g. if you have float data[16]; and you write data[2], then is that row-0/column-2, or is it row-2/column-0?

Just to clarify though, are the basis vectors in a row order matrix the rows or are they always columns?
That depends on what you mean.

Row-major and column-major ordering generally refer to the computer science topic of how you map a 2D array to a 1D array. This is just an internal detail of your math library of how it decides to store the matrix elements in memory.

Row-vectors and column-vectors generally refer to the math topic of whether you're writing vectors horizontally or vertically... This choice actually does affect your math (e.g. do you write projection * view, or view * projection).

However, the terms "row major" and "column major" also sometimes get used to describe the mathematical conventions... which makes everything pretty confusing.

If someone is writing their basis vectors in the rows of a matrix, they might sometimes say that it's a "row major matrix" -- here they're talking about their math, not about computer science arrays :(

I found a blog post on the ryg blog that talks about matrix ordering and he says that whatever algorithm you use for matrix multiplication, it doesn't depend on the ordering of the matrices.
That's correct. The matrix multiplication operation does not care what is in the matrices. They might not contain basis vecotrs at all, but perhaps weighting of how much you like different ice-cream flavours. Regardless of what kind of mathematical convention you're using (whether you're writing your basis vectors horizontally or vertically), your matrix multiplication function will be implemented the same way.

However, your 1D-array-storage convention does matter. e.g. if you have float data[16]; and you write data[2], then is that row-0/column-2, or is it row-2/column-0?

Just to clarify though, are the basis vectors in a row order matrix the rows or are they always columns?
That depends on what you mean.

Row-major and column-major ordering generally refer to the computer science topic of how you map a 2D array to a 1D array. This is just an internal detail of your math library of how it decides to store the matrix elements in memory.

Row-vectors and column-vectors generally refer to the math topic of whether you're writing vectors horizontally or vertically... This choice actually does affect your math (e.g. do you write projection * view, or view * projection).

However, the terms "row major" and "column major" also sometimes get used to describe the mathematical conventions... which makes everything pretty confusing.

If someone is writing their basis vectors in the rows of a matrix, they might sometimes say that it's a "row major matrix" -- here they're talking about their math, not about computer science arrays :(

Okay, thanks for clarifying!!

This topic is closed to new replies.

Advertisement