Smooth Camera

Started by
8 comments, last by dpoon 16 years, 2 months ago
Hello, I always had problem with smooth camera rotations, but all the pc games I know have total smooth rotations if you move the mouse - so obviously the problem must be my code. My code of the camera rotation about the y-axis (so when the user moves the mouse to the left or right) is this:
void FreeCamera::yaw(double deltaX){ 
   D3DXMATRIX rotY;
   D3DXMatrixRotationY(&rotY, D3DXToRadian(deltaX * rotationSpeed));
   D3DXVec3TransformCoord(&dir, &dir, &rotY);

   D3DXVec3TransformCoord(&up, &up, &rotY);
   D3DXVec3Cross(&right, &up, &dir);
}

All I do is creating a y-axis rotation matrix and rotate the dir vector (the direction where the camera is looking) and the up vector (and at the end I recalculate the right vector). rotationSpeed is a constant which configures the rotation speed. And thats my application code where I call the yaw method:
if(g_mouseState.rgbButtons[1]) {	
   double x = fElapsedTime * g_mouseState.lX;
   double y = fElapsedTime * g_mouseState.lY;
		
   g_camera->yaw(x);
}

Again pretty straightforward: Every frame I check if the right mousebutton is pressed and if yes then I catch the current relative movement of the mouse, multiplicate it by the elapsed frame time and pass it to yaw. To me this code seems ok but when I move the mouse the rotation of the camera isnt smooth - its jumpy. I guess you guys must had the same problems, so whats the problem with my code and what would be a better code? Thanks!
Advertisement
The problem is that usually mouse movement is jumpy, so if you're going to model the camera's motion directly from the mouse movement, it will be jumpy too. Usually you'll want to de-couple the camera movement from the exact movement of the mouse. This means you'll want to damp the camera movement very slightly (i.e., use a spring), or interpolate from current to target positions, anything to smooth out the movement function. I.e., consider your existing code the code that computes the camera's "target" position/rotation. Now just insert code that makes the actual camera position/rotation get there over several frames, smoothly from wherever it currently is.
Hm ok, I get the idea. But I have some problems by implementing this "smoothing function". Should I take the average relative mouse mouse movement of the last N frames?
It would be great if you could show me some (pseudo)code to help me getting started.
Alright, I just threw this together, totally untested and un-compiled. The idea is to still do everything you were doing before, but now instead of using your dir, up, and right variables, use new ones (I called them "realDir", "realUp", and "realRight"). Interpolate toward your target values each frame.

Here's some sample source. Ultimately your smoothing function could be more sophisticated. Additionally, if you stored your camera's orientation as a quaternion, things might work a little better, as you could do a single lerp and you wouldn't have to worry about your camera basis getting skewed during the interpolation. As it is, this should still work as long as you're not lerping between radically different vectors.

class FreeCamera{	// ...	// add this:	D3DXVECTOR3 realDir, realUp, realRight;	// ...};void FreeCamera::yaw(double deltaX){    D3DXMATRIX rotY;   D3DXMatrixRotationY(&rotY, D3DXToRadian(deltaX * rotationSpeed));   D3DXVec3TransformCoord(&dir, &dir, &rotY);   D3DXVec3TransformCoord(&up, &up, &rotY);   D3DXVec3Cross(&right, &up, &dir);	// add this:   const f32 lerpSpeed = 0.125f; // play with this value, smaller for slower but smoother, bigger for faster/jerkier   D3DXVec3Lerp( &realDir,		&realDir,	&dir,	lerpSpeed );   D3DXVec3Lerp( &realUp,		&realUp,	&up,	lerpSpeed );    D3DXVec3Lerp( &realRight,	&realRight,	&right, lerpSpeed );    // now, where you used dir, up, and right, use realDir, realUp, and realRight instead...   // you might also want to throw some D3DXVec3Normalize on realDir, realUp, realRight after the lerp}
Thanks for your code emeyex!

But doesnt your code just decrease my rotation speed instead of performing a real interpolation? I mean if I rotate the dir vector about 45° then your lerp performs a lerp between the rotated dir vector (the true vector) and the realDir vector, so in the end the result is closer to realDir and the rotation is just "decreased".
And with which values should I initialize realDir, realUp and realRight? I tried the same values as dir, up, right and setting them to zero vectors - neither worked.
It should indeed slow down the rotation, with the goal being that if you do any quick, jerky movement, the camera won't actually hit the apex of any of those motions. Imagine you were moving the mouse quickly back and forth, then it should never actually reach its "target" because the interpolation would slow it down enough; until you actually settle on something (and stop moving the mouse), then the camera should reach the target.

Quote:And with which values should I initialize realDir, realUp and realRight? I tried the same values as dir, up, right and setting them to zero vectors - neither worked.

When you say neither "worked," what does that mean?
Quote:Original post by emeyex
When you say neither "worked," what does that mean?

I had a really, really strange bug (just by adding the realDir vector my scene disappeared - I just saw the clear color) but it works now.

Unfortunately my movement is still jumpy. I changed my yaw method to this:
void FreeCamera::yaw(double deltaX){    D3DXMATRIX rotY;   D3DXMatrixRotationY(&rotY, D3DXToRadian(deltaX * rotationSpeed));   D3DXVec3TransformCoord(&dir, &dir, &rotY);   D3DXVec3TransformCoord(&up, &up, &rotY);   D3DXVec3Cross(&right, &up, &dir);   const float lerpSpeed = 0.025f;    D3DXVec3Lerp( &realDir,	&realDir,	&dir,	lerpSpeed );   D3DXVec3Lerp( &realUp,	&realUp,	&up,	lerpSpeed );    D3DXVec3Lerp( &realRight,	&realRight,	&right, lerpSpeed ); } 

my pitch method (for looking down and up if the mouse is moved on the y-axis) to this:
void FreeCamera::pitch(double deltaY) { 	D3DXMATRIX rotAxis;	D3DXMatrixRotationAxis(&rotAxis, &right, D3DXToRadian(deltaY * rotationSpeed));   D3DXVec3TransformCoord(&dir, &dir, &rotAxis);   D3DXVec3TransformCoord(&up, &up, &rotAxis);   const float lerpSpeed = 0.025f;    D3DXVec3Lerp( &realDir,	&realDir,	&dir,	lerpSpeed );   D3DXVec3Lerp( &realUp,	&realUp,	&up,	lerpSpeed );    D3DXVec3Lerp( &realRight,	&realRight,	&right, lerpSpeed );}

and in all my camera translation methods I am now using the realXY vectors, for example:
void FreeCamera::moveForward(double deltaTime) {   eye += realDir * translationSpeed * deltaTime;}

Did I make something wrong? Do you have an idea why its still jumpy (even if I constantly move the mouse in one direction the camera rotation jumps)

[Edited by - schupf on February 17, 2008 11:03:51 AM]
One quick note is that you really only want to do the "lerps" once per frame per vector. So, you might take it out of the yaw and pitch functions and create your own method "LerpTowardTarget" or something. First perform your yaw and pitch, then perform one LerpTowardTarget per frame. If it still feels jumpy, you might try making "lerpSpeed" even smaller. If it doesn't feel any different than before, than double-check that you're actually using "realDir", "realUp", etc when constructing your view/camera matrix (i.e., when you go to render your objects, they should be using the "real" vectors to put them in camera space).
Also, you should probably apply the same "lerping to target" principle for the eye as well.
What kind of camera are you implementing? A first person camera or a third person camera? For the latter your best bet is to use some kind of spring system like emeyex suggested. There's a good forum posting here that shows how this is done. For a first person camera this article might be helpful.
http://www.dhpoware.com

This topic is closed to new replies.

Advertisement