Quaternion camera performs unwanted roll

Started by
10 comments, last by BlackJoker 9 years, 6 months ago

Hey guys,

I've been trying to figure this out all day and I know that there quite some ressources on that rotation/quaternion stuff out there but I still couldn't figure out the problem. I'm creating an editor in DirectX 11 and are currently doing a camera that can be rotated and moved around using the mouse. Moving the camera works fine. For the rotation I want the user to be able to hold down the right mouse button and perform a free look around. In the code, I chose to use quaternions as I read that they would have quite some advantages compared to other rotation techniques. My code almost works as it should but, although the expression "input.m_rotation.z * ROTATION_SPEED.z" in the code below currently always evaluates to 0, there is still some roll occurring when I rotate the camera. I can't figure out where the problem lies, so I hope you have an idea and can show me what I'm missing or doing wrong. I don't really have experience in using quaternions, so if you can share some knowledge on how to improve the code below in general or on alternate implementations, that would be great, too.

(the "input" variable in the code contains the delta movement/rotation to be applied for the current frame based on user input)


XMVECTOR pitchQuaternion = XMQuaternionRotationAxis(XMLoadFloat3( &m_right ), XMConvertToRadians( input.m_rotation.x * ROTATION_SPEED.x));
XMVECTOR yawQuaternion = XMQuaternionRotationAxis(XMLoadFloat3( &m_up ), XMConvertToRadians( input.m_rotation.y * ROTATION_SPEED.y));
XMVECTOR rollQuaternion = XMQuaternionRotationAxis(XMLoadFloat3( &m_lookAt ), XMConvertToRadians( input.m_rotation.z * ROTATION_SPEED.z));

XMVECTOR rotationQuaternion = XMQuaternionMultiply(yawQuaternion, pitchQuaternion);
rotationQuaternion = XMQuaternionMultiply(rotationQuaternion, rollQuaternion);

XMMATRIX matTranslation = XMMatrixTranslationFromVector(-XMLoadFloat3(&input.m_movement) * XMLoadFloat3(&MOVEMENT_SPEED));
XMMATRIX matRotation = XMMatrixRotationQuaternion(XMQuaternionConjugate(rotationQuaternion));

XMMATRIX matTransform = XMMatrixMultiply(matTranslation, matRotation);
XMStoreFloat4x4(&m_viewMatrix, XMMatrixMultiply(XMLoadFloat4x4(&m_viewMatrix), matTransform));

If you need more information, please ask. Thank you very much in advance for your effort!

Advertisement

It looks very similar to the way I did my mouse look code as well. Here are the relevant snippets you might be interested in:

Camera3D:


/// <summary>
/// This increments the camera look-at position by the given radian angles:
///  [yaw, pitch, roll]
/// </summary>
/// <param name="d_xyz">these are the angles to change orientation by.</param>
public void RotateBy(Vector3 d_xyz)
{
m_rotation *= Quaternion.CreateFromAxisAngle(Vector3.Up, d_xyz.X) * Quaternion.CreateFromAxisAngle(Vector3.UnitZ, d_xyz.Y) * Quaternion.CreateFromAxisAngle(Vector3.UnitX, d_xyz.Z);

m_lookAt = m_position + Vector3.Transform(Vector3.UnitX, m_rotation);
m_up = Vector3.Transform(Vector3.Up, m_rotation);

UpdateView();
UpdateFrustum();
}

MouseLook Code:


if (Mouse.GetState().RightButton == ButtonState.Pressed)
{
	int yaw = Mouse.GetState().X - m_lastMouse.X;
	int pitch = Mouse.GetState().Y - m_lastMouse.Y;
	if (yaw != 0 || pitch != 0)
	{
		if (Keyboard.GetState().IsKeyDown(Keys.LeftShift))
		{
			m_sceneDB.ActiveCamera.RotateBy(new Vector3(MathHelper.ToRadians(-yaw / 10.0f), MathHelper.ToRadians(-pitch / 10.0f), 0));
		}
		else if (Keyboard.GetState().IsKeyDown(Keys.LeftControl))
		{
			m_sceneDB.ActiveCamera.RotateBy(new Vector3(MathHelper.ToRadians(-yaw * 3.0f), MathHelper.ToRadians(-pitch * 3.0f), 0));
		}
		else
		{
			m_sceneDB.ActiveCamera.RotateBy(new Vector3(MathHelper.ToRadians(-yaw), MathHelper.ToRadians(-pitch), 0));
		}
		
		Mouse.SetPosition(m_lastMouse.X, m_lastMouse.Y);
	}
}

I think one difference between my implementation and yours is that I am simply adding a delta rotation to an existing quaternion rotation and your implementation is creating a new quaternion rotation based on the yaw, pitch and roll values. I'm not sure if that's where the error is coming from, but that might be a good place to look first.

You can also decide to manually set the roll to zero each frame, but that's kind of a hackish solution.

Where are m_right, m_up, and m_lookAt coming from? Could we see where/how they get updated?

If you are using free look, and you look up a little and then move the mouse straight left, you will end up with the horizon slanted. Is that what you mean? To avoid that you have to take extra steps which I can't remember right now.

First of all: Thanks guys for your swift answers!

The question of NumberXaero brought me on the right track because currently I'm actually not updating the m_right, m_up, and m_lookAt vectors at all. That's probably the reason for the weird behaviour of the camera. I can't believe I didn't think of that myself, I assume I just sat too long in front of that code yesterday.

So, how can I update these vectors if I have a quaternion for the camera rotation? Can I simply multiply them with the quaternion?

Cheers guys!

Ok, I've figured it out myself, kind of...

Updating the lookAt and up vector for the given rotation quaternion works just fine and the camera seems to behave just as I want it to. But when I additionally update the right vector in a similar fashion (which I thought should also be necessary) I get this unwanted roll again when rotating the camera. I also tried to get the new right vector by calculating the cross product of up and lookAt vector but that didn't improve anything. I'm posting the code below. Any hints?


XMVECTOR v = XMQuaternionMultiply(rotationQuaternion, XMLoadFloat3( &m_up ));
XMStoreFloat3(&m_up, XMQuaternionMultiply(v, XMQuaternionConjugate(rotationQuaternion)));
	
v = XMQuaternionMultiply(rotationQuaternion, XMLoadFloat3( &m_lookAt ));
XMStoreFloat3(&m_lookAt, XMQuaternionMultiply(v, XMQuaternionConjugate(rotationQuaternion)));
	
// if I include the two lines below I get the unwanted roll, if I leave it commented out the camera seems to work properly
v = XMQuaternionMultiply(rotationQuaternion, XMLoadFloat3( &m_right ));
XMStoreFloat3(&m_right, XMQuaternionMultiply(v, XMQuaternionConjugate(rotationQuaternion)));

// This is what I tried as an alternative to the line above, with no success
XMStoreFloat3(&m_right, XMVector3Cross(XMLoadFloat3(&m_up), XMLoadFloat3(&m_lookAt)));

I don't even use a right vector in my camera code. I figure that the "up" and "lookat" vectors are enough. If I need a right vector, I can derive it from those two vectors by using a cross product. The only time I'd really need a right/left vector is for moving the camera on its lateral axis. But, that can be accomplished with a transform as well. Here is my code for panning the camera which demonstrates this:


/// <summary>
/// This pans the camera along its left direction vector by an amount
/// </summary>
/// <param name="amount">The amount to pan by (use negative values to pan right)</param>
public void Pan(float amount)
{
	Vector3 DirVec = Vector3.Transform(Vector3.Forward, m_rotation) * amount;
	m_position += DirVec;
	m_lookAt += DirVec;
	UpdateView();
	UpdateFrustum();
}

But what about the pitch rotation? I thought you would need the current "right" vector to properly calculate that rotation. It's just weird that it works perfectly without doing anything to the right vector and I don't like that I don't really understand it.

Ive had this problem before when using input values to incrementally rotate. The way I fixed it was to track the total rotation about each axis, and then rebuild the orientation each frame. So...

// Get Input
vec3 pitchYawRoll; // add up your angles here, clamp, wrap, convert to radians, etc

// Updating
quat qPitch, qYaw;
qPitch.FromAxisAngle(UNIT_X, pitchYawRoll.x); // build pitch
qYaw.FromAxisAngle(UNIT_Y, pitchYawRoll.y); // build yaw

quat qPitchYaw = qYaw * qPitch;
vec3 forward = qPitchYaw * UNIT_Z; // create forward

quat qRoll;
qRoll.FromAxisAngle(forward, pitchYawRoll.z); // roll about current forward

quat final = qRoll * qPitchYaw;

// then if you want up and right

vec3 right = final * UNIT_X;

vec3 up = final * UNIT_Y;

// or calc 1 of the above and do cross product with forward to get the other

Your rotation order may be different, etc, but thats the general idea I use.

I went with a similar solution to what NumberXaero posted and it worked fine. Thanks everybody for your effort!

This topic is closed to new replies.

Advertisement