Sign in to follow this  
redneon

Modifying the view matrix

Recommended Posts

I have a free camera class used for observing 3D models in a demo of mine. It contains the view and projection matrices I pass to my shaders for rendering. So far I've been storing position, pitch and yaw as member variables inside the camera class and then on update I've been doing something like this to create my view matrix for rendering:

[code]
mPitch += GetControllerPitch();
mYaw += GetControllerYaw();

Matrix rotation = Matrix::CreateXRotation(mPitch);
rotation = Matrix::CreateYRotation(mYaw) * rotation;

Vector& left = rotation.GetColumn(0);
Vector& up = rotation.GetColumn(1);
Vector& forward = rotation.GetColumn(2);

if (GetControllerUp())
mPosition += forward;
if (GetControllerDown())
mPosition -= forward;
if (GetControllerLeft())
mPosition -= left;
if (GetControllerRight())
mPosition += left;

mView = Matrix::CreateLookAt(mPosition, mPosition+forward, up);
[/code]

I don't like this, though. I don't like storing the total position, pitch and yaw values in the class. I should be able to get all this information directly from the view matrix, shouldn't I? I'm effectively storing the information twice. I was wondering if I can do away with calling CreateLookAt() every frame and just multiply the view matrix by the relative changes each frame. Something like this:

[code]
float pitchChange = GetControllerPitch();
float yawChange = GetControllerYaw();

Matrix rotation = Matrix::CreateXRotation(pitchChange);
rotation = Matrix::CreateYRotation(yawChange) * rotation;

mView = rotation * mView;

Vector& left = mView .GetColumn(0);
Vector& up = mView .GetColumn(1);
Vector& forward = mView .GetColumn(2);
Vector& position = mView.GetColumn(3);

if (GetControllerUp())
mPosition += forward;
if (GetControllerDown())
mPosition -= forward;
if (GetControllerLeft())
mPosition -= left;
if (GetControllerRight())
mPosition += left;
[/code]

Should this be possible or do I need to take other things into consideration?

Share this post


Link to post
Share on other sites
Probably the part where the view matrix is the inverse of the cameras transformation, so if you want to use only the view matrix without inverting back and forth all the time, you will have to apply everything in reverse order and direction.

Share this post


Link to post
Share on other sites
What speaks against storing these values directly in the class? Saving a few bytes of
space? Making your class feel more
'professional'? Maybe its just me, but it feels akward using the view matrix to store and access camera data.

Share this post


Link to post
Share on other sites
[quote name='The King2' timestamp='1318222516' post='4870967']
What speaks against storing these values directly in the class? Saving a few bytes of
space? Making your class feel more
'professional'?
[/quote]

I think that's it, yeah. I want to do things the "proper" way, really. Plus, I think I'd like to get more of a feel for how transformations affect the view matrix. If I use CreateLookAt() every frame then all of that is abstracted from me.

I'm at work at the minute but I'll try applying the inverse of the rotation and translation changes when I get home and see if I have any luck. Presumably all I need to do is reverse the direction supplied by the controller? I.e., if they press left on the controller then translate by positive X rather than negative X?

Share this post


Link to post
Share on other sites
You also need to reverse the order of transformations. You can't easily do that in your case, because you reduced the whole multiplication with a translation matrix to directly modifying the position.

Also, lookAt doesn't hide much. It builds a transformation matrix that is right/up/forward/position (just what you already have anyway) and then inverts it (usually in a simplified way, because you don't care about a generic matrix inversion for this.

Share this post


Link to post
Share on other sites
[quote name='Trienco' timestamp='1318263408' post='4871110']
You also need to reverse the order of transformations. You can't easily do that in your case, because you reduced the whole multiplication with a translation matrix to directly modifying the position.

Also, lookAt doesn't hide much. It builds a transformation matrix that is right/up/forward/position (just what you already have anyway) and then inverts it (usually in a simplified way, because you don't care about a generic matrix inversion for this.
[/quote]

Hmm. I think I've got it basically working but I'm getting a anomaly. After I've got the relative changes to pitch, yaw and translation I'm doing this to get the resulting view matrix:

[code]
Matrix translation = Matrix::CreateTranslation(xChange, yChange, zChange);
Matrix rotation = Matrix::CreateXRotation(pitchChange);
rotation = Matrix::CreateYRotation(yawChange) * rotation;

Matrix transform = rotation * translation;

mView = transform * mView;
[/code]

Does this look ok? I'm not sure why I was setting the position explicitly before. Anyway, the anomaly I'm getting is that the translation seems to work fine but when I pitch up and attempt to rotate to yaw around the scene it looks like it's rolling too. I've recorded a small video so you can see what I mean as it's easier than trying to explain it:

[media]http://www.youtube.com/watch?v=5sZPOw7zTxw[/media]

At first I'm just translating but then I pitch up you can see that when I'm rotating the yaw it appears to be rolling around to the side whereas what I'm wanting is it to rotate around the scene like a free look camera. I suspect it's not actually an anomaly but more than I'm missing something in my calculations.

Share this post


Link to post
Share on other sites
There are still two things. a) you didn't invert your order of rotation and b) you are applying your new transformation after instead of before the old transformation. Matrix muliplication is NOT commutative and A*B is not the same as B*A, in fact, you can think of A*B applying A using local coordinates and B*A applying A in world coordinates (ie. one rotation uses the objects current local axes and the other uses the worlds x,y,z axes).

But your biggest problem is that before you were building you rotation from scratch every frame and always just summed up the angles. Now you apply the new rotation to the previous rotation, which results in a totally different order of rotations and obviously doesn't get you the result you want. To get the same effect, one of the rotations needs to use a global axis (typical first person cameras will always use the worlds (0,1,0) because it would be plain wrong to rotate around the objects actual "up").

Share this post


Link to post
Share on other sites
Hidden
There are still two things. a) you didn't invert your order of rotation and b) you are applying your new transformation after instead of before the old transformation. Matrix muliplication is NOT commutative and A*B is not the same as B*A, in fact, you can think of A*B applying A using local coordinates and B*A applying A in world coordinates (ie. one rotation uses the objects current local axes and the other uses the worlds x,y,z axes).

But your biggest problem is that before you were building you rotation from scratch every frame and always just summed up the angles. Now you apply the new rotation to the previous rotation, which results in a totally different order of rotations and obviously doesn't get you the result you want. To get the same effect, one of the rotations needs to use a global axis (typical first person cameras will always use the worlds (0,1,0) because it would be plain wrong to rotate around the objects actual "up").

Share this post


Link to post
Yeah, I know that matrix multiplication isn't commutative. I've inverted the order of the rotation like this...

[code]
Matrix rotation = Matrix::CreateYRotation(yawChange);
rotation = Matrix::CreateXRotation(pitchChange) * rotation
[/code]
...but it made no difference to the actual rotation.

When you say I'm applying the new translation after the old one do you mean I should be doing this...

[code]
mView = mView * transform;
[/code]
? Because when I do this I get really strange results. The translation looks ok but the rotation is way off what I expect.

I'm using column-major pre-multiplied matrices which means that the order of multiplication goes from right to left, rather than left to right. Case in point, in the video you can see that the triangle has been translated away from the origin but it's rotating about it local Y axis. The transform for this is transform = translation * rotation. This performs the rotate and then the translation. If I did transform = rotation * translation then the triangle would rotate around the origin at a distance specified by the translation.

How can I perform the rotation about the world's up? I'm thinking I need to calculate the rotation change in local space (like I already am) and then multiplying that by the inverse of the rotation that's applied to the view matrix, maybe? Though I'm not sure how I'd do that even if it is the right thing to do.

Share this post


Link to post
Share on other sites
I think you're right about the rotation about the world. From what I can gather my rotations are working correctly. I just imagined what it would look like if I was a camera and I pitched up around an object and then rotated my head left and right to simulate the yaw and what I'm seeing looks about right. I think what I actually want is a first person camera that can fly around the scene rather than a free camera. Which I imagine is where your rotation about the world idea comes in?

Share this post


Link to post
Share on other sites
[quote name='wolfscaptain' timestamp='1318332252' post='4871413']
Quaternions fit 3D rotations much better then matrices. Especially if you want interpolations.
[/quote]

I haven't added a quaternion class yet but it's on my to-do list. I could add one to fix this problem but because I've hit a brick wall in my knowledge with this method I want to know how to solve it this way even if I do end up using quaternions in the future.

Share this post


Link to post
Share on other sites
I've fixed this and am just adding a reply in case I forget what I did or anyone else comes across this problem.

Basically, Trienco was right in that I needed to rotate the yaw about the world's up axis and this is what I did to achieve that:

[code]
//Transpose the rotation portion of the view matrix to get the inverse rotation.
Matrix viewRotation = mView;
mView.SetColumn(3) = Vector(0.0f, 0.0f, 0.0f, 1.0f); //Mask off the translation portion of the view matrix.
Matrix viewRotationInverse = viewRotation.GetTransposed();

Matrix yawRotation = Matrix::CreateYRotation(yawChange);
Matrix pitchRotation = Matrix::CreateXRotation(pitchChange);

//Apply the rotation changes. Rotate the yaw around the world's up axis and the pitch around the local left axis.
mView = viewRotation * yawRotation * viewRotationInverse * pitchRotation * mView;

//Orthonormalise in case of floating point error.
mView.Orthonormalise();

Matrix translation = Matrix44::CreateTranslation(xChange, yChange, zChange);
mView = translation * mView;
[/code]

I did have some issues with floating point error, which is why the call to Orthonormalise() is required, but other than that it all works fine.

Share this post


Link to post
Share on other sites
[quote name='redneon' timestamp='1318512130' post='4872192']
I did have some issues with floating point error, which is why the call to Orthonormalise() is required, but other than that it all works fine.
[/quote]

This might be slightly less of a problem with quaterions (though they still need to be unit length too).

But I'm a big fan of understanding the principle behind it before going "this is too complicated, I'll just copy/paste this quaternion stuff, it's way easier". If dealing with rotation matrices is too complicated for some, how is it easier to deal with multidimensional complex numbers and a theory so unintuitive that the world of math has ditched the whole concept in favour of vectors and matrices over a hundred years ago? They have their uses, but it's kind of depressing when you see people use them for everything, because they're "cool" and end up creating a convoluted and overcomplicated mess by not having any idea what they are doing (and just applying trial and error until it seems to be right). Plus, whatever benefit you get out of them must be worth the overhead of all the conversions.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this