Math Behind Look At Matrix 4x4

Started by
10 comments, last by Neosettler 12 years, 2 months ago
Greetings,

I came across a math problem that mystifies me and I can’t sleep very well since then. Taking the function below, wish seems to be a common practice to get a camera view in a 3d world using C++.


template <typename T>
GLM_FUNC_QUALIFIER detail::tmat4x4<T> lookAt
(
detail::tvec3<T> const & eye,
detail::tvec3<T> const & center,
detail::tvec3<T> const & up
)
{
detail::tvec3<T> f = normalize(center - eye);
detail::tvec3<T> u = normalize(up);
detail::tvec3<T> s = normalize(cross(f, u));
u = cross(s, f);

detail::tmat4x4<T> Result(1);
Result[0][0] = s.x;
Result[1][0] = s.y;
Result[2][0] = s.z;
Result[0][1] = u.x;
Result[1][1] = u.y;
Result[2][1] = u.z;
Result[0][2] =-f.x;
Result[1][2] =-f.y;
Result[2][2] =-f.z;

Result[3][0] =-dot(s, eye);
Result[3][1] =-dot(y, eye);
Result[3][2] = dot(f, eye);
}


If my understanding is correct, m[3][0], m[3][1] and m[3][2] refers to the XYZ position in a Column-Major matrix. What’s making me nervous is, when passing the object position, for instance: eye(0, 5, 20) becomes (0, 0, 20.61) after being transformed. How is that possible that the Matrix transforms the actual position?

In my book, it should be :

Result[3][0] =- eye.x
Result[3][1] = -eye.y
Result[3][2] = -eye.z


What am I missing?
Advertisement
It has to do with the order of multiplication of the rotation and translation matrix that's used by the LookAt matrix.

So if you have

rotation =
[ leftVector, 0 ]
[ upVector, 0 ]
[ fVector, 0 ]
[ 0 , 0, 0, 1 ]

translation =
[ 0 0 0 ]
[ 0 0 0 translationVector ]
[ 0 0 0 ]
[ 0 0 0 1 ]

and your order of multiplication is rotation*translation, then the result for the last column in the lookat matrix will have the dot products.
Its important to keep this order of multiplication for the lookAt matrix to have the desired effect - because you want to specify your eye position in "global coordinates"
If you reverse the order - the translation will be affected by the rotation.
Thank you for your response ne0,

It has to do with the order of multiplication of the rotation and translation matrix that's used by the LookAt matrix.

While that make sense, I’m not out of the woods yet. If it is not too much to ask, I’m wondering why you`ve mentioned one matrix for rotation and one for translation? I was under the impression the look at matrix combines rotation and translation all together and all you need is the eye, center and the up vector.

I’m also guessing you meant:

translation =
[ 0 0 0 X]
[ 0 0 0 Y]
[ 0 0 0 Z]
[ 0 0 0 1 ]

Taking into consideration that the last column holds the translation as most Matrix classes implementation I’ve seen so far use GetTranslation() return vector3(m[3][0], m[3][1], m[3][2]). The look at matrix confuses me…

Thank you for your response ne0,

It has to do with the order of multiplication of the rotation and translation matrix that's used by the LookAt matrix.

While that make sense, I’m not out of the woods yet. If it is not too much to ask, I’m wondering why you`ve mentioned one matrix for rotation and one for translation? I was under the impression the look at matrix combines rotation and translation all together and all you need is the eye, center and the up vector.



Exactly, the LookAt matrix combines rotation and translation - hence it could be decomposed into a rotation matrix and translation matrix which when multiplied together yield the LookAt matrix.

Since you were wondering why the last column of the LookAt matrix takes dot products and not just eye's x, y, z - the reason is exactly because the whole LookAt matrix is composed in one step, so you don't see the actual multiplication of the rotation and translation matrices.

I think what you might be forgetting is that this matrix is not applied to the camera - but the objects themsleves.
So imagine you want to rotate 90 degrees to the left :
[attachment=7243:Untitled.png]

So imagine now you want to also move the camera's eye position by 1 unit backwards. Would it make any sense to move the object on z axis?
No - you need to translate it one unit on the x axis - which is where the dot products came into place.

I hope that makes any sense, because I feel I'm explaining it very poorly. Someone else (like jyk) would be able to explain it better perhaps...
Just because my computer graphics course just barely went over cameras, here's a link to the slides, and here's a link to the video of the lecture. The video will probably make a bit more sense, seeing as you have the instructor explaining things.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Thank you for your inputs and tutorial, it is very appreciated.

After investigation and thanks to you guys, I came to the conclusion that the LookAtMatrix should never be edited explicitly but rather composed from the Inverse Matrix of the camera world’s rotation and translation.


void Math::SetLookAtMatrix(Matrix4f &in_matrix, const Quaternf & in_rotation, const Vector3f &in_origin)
{
in_rotation.SetMatrix(in_matrix);
in_matrix.Inverse();
in_matrix.SetTranslation(in_matrix * -in_origin);
}


You might ask why? Simply because I’m working on a 3D Editor type of camera like Maya. Therefore, gathering mouse inputs X,Y to OrbitAroundTarget opens up a new logistic dilemma. I’m wondering how to OrbitAroundTarget by tranforming the actual world's rotation and translation of the camera without operating on the LookAtMatrix directly. Needless to say that gimbal locks are to be avoided.

Any suggestions and/or common practices would be beneficial as I’ve been banging my head over this for months.
There is nothing wrong in composing the LookAtMatrix directly - all you are doing is saving the matrix multiplication.

However, using the LookAtMatrix, as you may have found out, is not always as practical as just composing the matrix out of rotation and translation.

In your particular case, OrbitAroundTarget could be implemented as easily with either I guess.
Use the following transformation to rotate around any point :

// Rotate around the target point
Matrix transform = Matrix().translate(target)*Matrix().rotate(angle, rotationVector)* Matrix().translate(-target);

In my opinion a good generic camera class should contain just the camera transformation (and support methods for LookAt and translation/rotation) and allow you to set any matrix as the camera's transform.
You can then inherit from this class and make e.g FpsCamera, OrbitAroundTargetCamera... which handle all the additional stuff.

Sorry If I'm stating something you already know :)
Hi Neo, thanks again for your inputs, it helps a lot. I’m sure you are doing a favor to other folks out there too.

There is nothing wrong in composing the LookAtMatrix directly - all you are doing is saving the matrix multiplication.
[/quote]

You are right but my camera use parenting and it makes things fairly more complicated, at least for me. For those interested, I update a local and a global transformation matrix. Conceptually, I choose not to interact with the matrix themselves directly and keep:
Vector3 LocalTranslation;
Vector3 LocalScaling;
Quaternion LocalRotation;
Matrix4 LocalTransform;

Vector3 WorldTranslation;
Vector3 WorldScaling;
Quaternion WorldRotation;
Matrix4 WorldTransform;


As an example, for the camera Dolly I do:
void Camera::Dolly(Float in_val)
{
Vector3f l_vector(m_Normal * m_SpeedDolly * in_val);

LocalTranslation += l_vector;
}


That’s why I need to apply the OrbitAroundTarget to the Translation/Rotation and update the matrices from the new OrbitAroundTarget coordinates. I couldn’t manage to find the right combination so far and I can assure you, I tried millions.

To resume, I’m looking to get the corresponding result of this:

Matrix transform = Matrix().translate(target)*Matrix().rotate(angle, rotationVector)* Matrix().translate(-target);[/quote]

And apply it to:

Quaternion LocalRotation;
Vector3 LocalTranslation;

So I can build the transformation matrices like so:
Int Transform::Update()
{
m_Rot.SetMatrix(m_Matrix);
m_Matrix.Scale(m_Scl);
m_Matrix.SetTranslation(m_Trs);
return 0;
}


I’m going to chew on your formula for a while...

So you can't use the formula because you forced the order of rotation/scalation/translation multiplication?
This is possible, but I think forcing the order is not a good idea (because it makes simple things complicated).
You could provide a default multiplication order, but still allow for it to be changed.

To answer your question :

Compute the transformation as I said above.
Then, set the

LocalTranslation to the translation vector of the transformation.


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]Compute the quaternion as you do usually,[/font]



[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]I have not tested this [/font][color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]

and some of your math function's names are a bit vague, but its something worth trying [/font]smile.png


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]The idea is that you would still be rotating around (0,0,0), but then you will translate the objects to their proper locations.[/font]


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]Also its very important for the SetTranslation method to set the matrix's translation vector and not multiply it by a translation matrix (in order for the above to work).[/font]



[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]If [/font][color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]you still have problems, it would be helpful if you could tell me what you are trying and what the observable result is.[/font]

This topic is closed to new replies.

Advertisement