Jump to content
  • Advertisement
Sign in to follow this  
Dakattack64

DirectX Smooth Third-Person Camera

This topic is 855 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

 Hello all!

 

I'm attempting to write a smooth third-person camera for a DirectX game I am making. My implementation uses a camera target that floats above the player's head, lerping to the player's X and Z location when the player moves. The camera itself is always a set distance away from the target, and calls "LookAt" on the target.

 

The camera currently follows the player smoothly upon translations, but rotations were jittery so I wanted to use quaternions and SLERP to make a smoother rotation around the player. However, with my current implementation below I seem to get strange rotations the more I rotate the camera. This leads me to believe that I am using the wrong axes or am encountering gimbal lock. Is there anything obviously wrong with my math? Or could it be a separate issue? Thanks in advance for your input.

 

Side Problem: How would I slerp between the two quaternions smoothly? What would I pass in for the ratio other than a fixed float? It seems impossible to slerp between two things in a single frame. I could do it over multiple frames, but the rotations would be reset after the first frame. So how would I accomplish this?


// Decompose the matrix to get the original rotation
XMVECTOR trans;
XMVECTOR scale;
XMVECTOR rotation;
XMMatrixDecompose(&scale, &rotation, &trans, XMLoadFloat4x4(&m_camTarg.GetLocalMatrix()));
XMVECTOR newRotation = rotation;
 
// Using the local X axis and the global Y for rotation; pitch and yaw are based on mouse rotation (delta)
XMVECTOR rotLocalX = XMQuaternionRotationAxis(XMLoadFloat3(m_camTarg.GetLocalXAxis()), m_fPitch);
XMVECTOR rotGlobalY = XMQuaternionRotationAxis(XMLoadFloat3(&XMFLOAT3(0.0f, 1.0f, 0.0f)), m_fYaw);
 
// Multiply the quaternions into the original rotation
newRotation = XMQuaternionMultiply(newRotation, rotLocalX);
newRotation = XMQuaternionMultiply(newRotation, rotGlobalY);
 
// Sluuurrrrpp!
newRotation = XMQuaternionSlerp(rotation, newRotation, 0.5f);
 
// Generate a new matrix with the new rotation
XMMATRIX newMat = XMMatrixAffineTransformation(scale, XMLoadFloat3(&XMFLOAT3(0.0f, 0.0f, 0.0f)), newRotation, trans);
 
// Set local matrix to new matrix with new rotation?
m_camTarg.SetLocalMatrix(newMat);
 
// Move the camera based on the local axes of the camera target
Vector3 camOffsetDirection = Vector3::Zero;
 
camOffsetDirection += *m_camTarg.GetLocalXAxis() * m_camOffset.x; // m_camOffset = (0.0f, 0.0f, -7.0f)
camOffsetDirection += *m_camTarg.GetLocalYAxis() * m_camOffset.y;
camOffsetDirection += *m_camTarg.GetLocalZAxis() * m_camOffset.z;
 
SetPosition(*m_camTarg.GetPosition() + camOffsetDirection);
 
LookAt(*GetPosition(), *m_camTarg.GetPosition(), Vector3::UnitY);
Edited by Dakattack64

Share this post


Link to post
Share on other sites
Advertisement

Have you tried not using XMMatrixDecompose(), but storing camera trans+rot as vectors (you must have them since you're doing a LookAt() to build the matrix)?

My thought is that the decomposed angles aren't correct, maybe because of angle limitations inherent from decomposition?

 

You could also try monitoring the angles output from XMMatrixDecompose() and newRotation and see what's happening?

 

Or maybe you could try XMQuaternionRotationRollPitchYaw(0, m_fPitch, m_fYaw) instead of those two rotations you're doing? - The angles you rotate about seems strange to me, one is straight (0, 1, 0) but the other is potentially not at a straight angle from the straight one (which in my book would give strange rotations)..

Share this post


Link to post
Share on other sites

Have you tried not using XMMatrixDecompose(), but storing camera trans+rot as vectors (you must have them since you're doing a LookAt() to build the matrix)?

My thought is that the decomposed angles aren't correct, maybe because of angle limitations inherent from decomposition?

 

You could also try monitoring the angles output from XMMatrixDecompose() and newRotation and see what's happening?

 

Or maybe you could try XMQuaternionRotationRollPitchYaw(0, m_fPitch, m_fYaw) instead of those two rotations you're doing? - The angles you rotate about seems strange to me, one is straight (0, 1, 0) but the other is potentially not at a straight angle from the straight one (which in my book would give strange rotations)..

I have peeked inside the newRotation and they look like valid values. In a previous implementation I was using XMQuaternionRotationRollPitchYaw() but because it uses world axes it wasn't rotating about the local X. This would cause problems where I wouldn't be able to rotate around the X axis when facing the player's left or right side (because I would be looking straight down the world X).

 

Hence, the reason why I am using the two separate axes for rotation; you want to rotate around the global Y axis, but the local X axis. Using global X won't allow us to change our pitch in all orientations, and using local Y will cause us to orbit in a strange manner. I could probably explain it better on paper.

 

I think that my problem might be caused by large delta mouse movements when the cursor goes outside of the screen, as the camera looks fine until I move it outside of the window. I'm going to see if setting the cursor to the center of the window every frame fixes the issue.

Share this post


Link to post
Share on other sites

Ahh I think I get it now :)

 

For mouse delta movement, I use this method:

	RAWINPUTDEVICE rid;
	rid.usUsagePage = 0x01;
	rid.usUsage = 0x02;
	rid.dwFlags = RIDEV_NOLEGACY;
	rid.hwndTarget = Global_Hwnd;
	if (RegisterRawInputDevices(&rid, 1, sizeof(rid)) == FALSE)
		return ERR_ALLOCATION;

.. And reads it using:

// .. This is in your WndProc()

	case WM_INPUT:
	{
		UINT size;
		GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER));
		if (size > 0)
		{
			RAWINPUT *data = (RAWINPUT*)malloc(size);
			if (data)
			{
				GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER));
				if (data->header.dwType == RIM_TYPEMOUSE)
				{
					if (data->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
					{
						MouseX = data->data.mouse.lLastX;
						MouseY = data->data.mouse.lLastY;
					}
					else
					{
						MouseX += data->data.mouse.lLastX;
						MouseY += data->data.mouse.lLastY;
					}
				}
				free(data);
			}
		}
		return 0;
	}

It allows me to read mouse position without being clipped :D

 

Before that, I used to center the mouse too, but it's a bad solution to the problem in the long run :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!