Smooth rotation and movement of camera
#1 Members - Reputation: 146
Posted 01 May 2012 - 05:11 AM
To do the rotation I store the camera target in spherical coordinates and modify these spherical coords and then convert them to cartesian coords. and re-calculate the camera matrix. To move the camera around I simply modify the camera position and re-calculate the camera matrix.
So far everything works as I want it. But whenever I rotate my camera (each time I press a button I change the latitude/longitude of my camera target by one unit/angle) or move my camera around I get "jerky movements". Basically they are nowhere close to being smooth rotations and movements. If I press and hold a button that keeps rotating the camera I merely want this rotation to be smooth and the same if I were to press and hold a button which moves the camera in some direction. What exactly should I be looking into to be able to achieve this?
#2 Members - Reputation: 146
Posted 01 May 2012 - 12:33 PM
Furthermore even if I were to implement mouse movement it doesn't solve the "jerky" movement if I move around the camera itself in the scene.
#3 Members - Reputation: 1589
Posted 01 May 2012 - 01:05 PM
Seeing some code samples would probably help here.
#4 Members - Reputation: 1837
Posted 01 May 2012 - 01:10 PM
Consider that at time t=0, the camera is at (0,0). The camera moves at 1 unit per second. Say that at t=0 you receive a keystroke to move the camera, so you translate the camera by one unit. However, this translation occurs instantaneously. At t=0 the camera is at (0,0) and at t=1 the camera is suddenly at (0,1). It causes a visible jump.
The thing is, in that 1 second span of time, the screen refreshed 60 times or more (depending on frame rate). For 60 frames it drew the view from camera at (0,0), then suddenly switched to drawing the scene from camera at (0,1), causing a visual jump. What you could be doing instead, though, is tracking the frame time of when the scene is drawn, and using that frame time to interpolate between (0,0) and (0,1) so that intermediate frames drawn will show the camera at different locations in between (0,0) and (0,1)
You might read the old standby, Fix Your Timestep! or Javier Arevalo's Main Loop with Fixed Time Steps at the old Flipcode site for more information on how this works.
#5 Members - Reputation: 146
Posted 01 May 2012 - 06:02 PM
Perhaps your problem is caused by the step sizes you are taking when accumulating the new angles to the camera orientation? To get smoother rotations, apply the rotation in smaller steps, or decouple the increments and maintain a "currently rendered" orientation and a "desired target" orientation, between which you interpolate smoothly.
Seeing some code samples would probably help here.
Wouldn't applying smaller steps to my rotation cause the camera to rotate slower though? Or am I misunderstanding something here? I'll post the necessary code here:
Function to create camera matrix
void createCameraMatrix()
{
glm::vec3 camTargetCart = sphericalToCartesian(cameraTarget); //Convert the camera target's spherical coords. to cartesian.
glm::vec3 lookDir = glm::normalize(camTargetCart); //Look direction
glm::vec3 upDir = glm::normalize(glm::vec3(0.0f, 1.0f, 0.0f)); //Up direction of camera, aligned with world's y-axis at beginning.
glm::vec3 rightDir = glm::normalize(glm::cross(lookDir, upDir)); //Calculate remaining axis of camera
glm::vec3 perpUpDir = glm::cross(rightDir, lookDir); //Re-calculate up direction
//Create camera matrix
rotMat[0] = glm::vec4(rightDir, 0.0f);
rotMat[1] = glm::vec4(perpUpDir, 0.0f);
rotMat[2] = glm::vec4(-lookDir, 0.0f);
rotMat = glm::transpose(rotMat);
transMat[3] = glm::vec4(-cameraPos, 1.0f);
finalMatrix = rotMat * transMat;
//Give value to uniform in shader
glUseProgram(theProgram);
glUniformMatrix4fv(locCameraTest, 1, GL_FALSE, glm::value_ptr(finalMatrix));
glUseProgram(0);
}
Some examples of my functions that are called when a keystroke is received
void rotCamHorizontal(GLfloat angle)
{
cameraTarget.y = cameraTarget.y + angle; //Add angle units to camera target (in spherical coords.)
createCameraMatrix(); //Re-calculate camera matrix to account for modified camera target
}
void rotCamVertical(GLfloat angle)
{
cameraTarget.z = cameraTarget.z + angle; //Add angle units to camera target (in spherical coords.)
cameraTarget.z = glm::clamp(cameraTarget.z, -90.0f, 90.0f); //Clamp the pitch between +- 90 degrees
createCameraMatrix(); //Re-calculate camera matrix to account for modified camera target
}
void moveCamForward(GLfloat moveSpeed)
{
cameraPos = cameraPos+(sphericalToCartesian(cameraTarget)*moveSpeed);
createCameraMatrix();
}
void moveCamRight(GLfloat moveSpeed)
{
cameraPos.x = cameraPos.x + (rotMat[0].x*moveSpeed); //rotMat[0].x correspond to (row, column) = (1, 1)
cameraPos.y = cameraPos.y + (rotMat[1].x*moveSpeed); //rotMat[1].x correspond to (row, column) = (1, 2)
cameraPos.z = cameraPos.z + (rotMat[2].x*moveSpeed); //rotMat[2].x correspond to (row, column) = (1, 3)
createCameraMatrix();
}
Function where movement/rotation functions are called.
void keyboard(unsigned char key, int x, int y)
{
GLfloat angle = 1.0f;
GLfloat moveSpeed = 1.0f;
switch(key)
{
//Move forward
case 'w':
moveCamForward(moveSpeed);
break;
//Rotate right (yaw)
case 'd':
rotCamHorizontal(-angle);
break;
//Rotate up (pitch)
case 'q':
rotCamVertical(angle);
break;
//Strafe right
case 'x':
moveCamRight(moveSpeed);
break;
}
}
I'd also like to point out that I only initialize the camera matrix once as my program is running (even when it is idle and doing nothing). The only time I re-calculate it is as shown above when either the camera position or camera target are changed. I'm not sure of this matters though. Also thanks FLeBlanc for the suggestion, will definitely look into it. But as requested the code is above for people to see, any suggestions are appreciated.
Edited by Suen, 01 May 2012 - 06:07 PM.
#7 Members - Reputation: 146
Posted 01 May 2012 - 08:36 PM
Will do, haven't looked very much yet through the links posted by FLEBlanc (since it's pretty late over here) but I went through it a bit fast and it seems to describe how to do the interpolation.As suggested, look into interpolating your camera position and rotation. That is the way to achieve the smaller steps the first poster recommended, but without slowing the speed down.
From what I understand so far a linear interpolation would be sufficient for smooth camera movement while quaternion slerp would be sufficient for smooth camera rotation. Either way I will look more into this when I wake up. Thanks for the suggestions
Edited by Suen, 01 May 2012 - 08:38 PM.
#8 Members - Reputation: 146
Posted 02 May 2012 - 03:08 PM
I checked the link provided by FLeBlanc (Fix Your Timestep!) and did also take a look at the source code provided there. I pretty much did the same:
I created a function for interpolation. I then changed one of my camera movement functions, storing the old and new position and then using them, together with what I assume would be the elapsed time value, as arguments for the interpolation function. I then create the view matrix from the interpolated camera position. See this below
glm::vec3 interpolate(const glm::vec3 &start, const glm::vec3 &end, float alpha)
{
glm::vec3 interp;
interp.x = end.x*alpha + start.x*(1-alpha);
interp.y = end.y*alpha + start.y*(1-alpha);
interp.z = end.z*alpha + start.z*(1-alpha);
return interp;
}
void moveCamLeft(GLfloat moveSpeed)
{
glm::vec3 startPos(cameraPos);
glm::vec3 endPos;
endPos.x = cameraPos.x - rotMat[0].x;
endPos.y = cameraPos.y - rotMat[1].x;
endPos.z = cameraPos.z - rotMat[2].x;
cameraPos = interpolate(startPos, endPos, timeValue);
createCameraMatrix();
}
Now as far as I've understood from the posts here and from the links provided is that if I want a smooth movement from one position to another I would need to draw all values between the two positions where each one of these values would correspond to a frame being drawn. For example if we have 60 fps then the scene would be drawn from 60 different positions and then when the next frame is drawn I am at the final value. To do this I understood it as using a time-based value for my interpolation (where the time-based value would vary from 0 to 1, 0 corresponding to the start and 1 to the end position when I interpolate). But I am still quiet confused...exactly what am I supposed to keep time of? The amount of time it takes to draw a frame? Something else? I am also confused to when I start to measure the time and exactly in what function I should do so.
I feel this should be quite easy to understand (conceptually it is). I'm probably making it harder than what it is actually.
Edited by Suen, 02 May 2012 - 03:09 PM.
#9 Members - Reputation: 1837
Posted 02 May 2012 - 04:17 PM
double t = 0.0;
const double dt = 0.01;
double currentTime = hires_time_in_seconds();
double accumulator = 0.0;
State previous;
State current;
while ( !quit )
{
double newTime = time();
double frameTime = newTime - currentTime;
if ( frameTime > 0.25 )
frameTime = 0.25; // note: max frame time to avoid spiral of death
currentTime = newTime;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
integrate( currentState, t, dt );
t += dt;
accumulator -= dt;
}
const double alpha = accumulator / dt;
State state = currentState*alpha + previousState * ( 1.0 - alpha );
render( state );
}
Here, currentState and previousState represent the set of transforms for the camera and all objects in the world at time t(n-1) and t(n), where n is the current logic step. accumulator is used to accumulate the advancement of time, and is also used to track how far into the current logic step we are. Each iteration through the loop, the current time is compared to the last time stored from the last time through the loop, and the difference is applied to accumulator. Then the logic update portion iterates on accumulator; as long as accumulator is larger than the length of 1 logic step (dt, or 0.01 in this example) then a physics step is performed (objects are moved, etc...). As soon as accumulator drops below the value of dt, we know that we are "current" or caught-up on logic updates and can proceed to rendering. However, the value of accumulator will now tell us how far along into the next physics step we are, so we can use it as the timeValue to interpolate transforms.
#10 Members - Reputation: 398
Posted 02 May 2012 - 08:19 PM
Time delta is necessary to convert a velocity-in-seconds value into a velocity-in-frames value. When using mouse input, I divide the mouse delta by dt to get the pixels-per-second value. Buffer that over 15 frames, and average out the last 15 frames worth of velocities. Then multiply that by dt again to turn it back into the value for the frame.
The side effect of this is that the camera will have a bit of inertia after stopping movement of the mouse, but that tends to feel better than a harsh stop anyway.
#11 Members - Reputation: 146
Posted 03 May 2012 - 01:09 AM
Thanks for the suggestion DigitalFragment, I've never heard of that approach but it seems to make sense. However I'm lost now on what way to approach. Like you I thought at first that a smooth interpolation between the prev. position and the intended one would slow down the camera but as suggested above (and as well in a few other places I've been looking into) this does not seem to be the so. So...while I am actually still confused and have several questions to ask (about both yours and the suggested one by FLeBlanc) I would appreciate it if I could opinions on what approach I should take.
Finally I want to say that I "solved" my problem. I was playing around with being able to register multiple keyboard inputs in GLUT. Basically I have two callback functions which are registered whenever a button is pressed/not pressed. I then have a third function which describes what each keyboard button I want to use do (here is where I call my camera movement/rotation functions). I finally call this third function in my display method (the method responsible for doing all rendering stuff), basically it's called for each loop (each time a new frame is drawn). This results in both smooth translation and rotation as I wanted. Compare to what I had before; I had a keyboard function (see above) which, instead of getting called every frame, only got called when I pushed a button. The result was that when the next frame is drawn after the keyboard input, with a modified view matrix, I would see the movement of my camera, but with this effect of "stuttering" which I wanted to solve.
Now here's the thing.......I don't have the slightest bit of clue as to why this second approach I took work. So no I really haven't solved the problem. I have a working solution without really understanding what's going on behind the scenes. Or have I perhaps confused everyone with what I intended to do? Well now that I think of it it's not really a working solution at all. Right now it's working fine because my FPS locked down to something around 60 FPS due to VSync being turned on, however the refresh rates obviously vary from one PC to another so if I use this solution in a PC with better/worse performance (or with VSync turned off) I would end up getting slower/faster movement. I'm still just curious to why it works...for 60 FPS at least.
Anyhow I'm still at step one so before I go further what is the recommended way to go for? Smooth interpolation or what DigitalFragment suggested? Would smooth interpolation slow the camera down as DigitalFragment said and as I thought from the beginning?
#12 Members - Reputation: 398
Posted 03 May 2012 - 01:44 AM
If you want to rotate from 30 degrees to 40 degrees in one frame, but are interpolating, then you aren't going to hit 40, you are going to hit anywhere > 30 and < 40.
On the next frame, if you have stopped rotating, then the interpolation doesn't continue to 40, as you will want to interpolate from the previous value, to a value no different to the previous value. It does work to solve the issue, but it results in 'sudden termination' when you stop the rotation.
The other trick causes smoothing both when the rotation starts and when the rotation ends. As such, it causes the same lag initially, but while continuing to hold the rotate button, that lag disappears.
I'd suggest trying both and seeing which feels better for your input system. It largely depends on what sort of game you are making, the choice here is really a design issue not an implementation one.
#13 Members - Reputation: 146
Posted 03 May 2012 - 06:01 PM
FLeBlanc, I do have several questions I want to ask about the particular implementation you posted there from "Fix Your Timesteps!". I've read through it about 3-4 times now and still have some problems grasping exactly everything going on there (I do understand it a bit better now but still mostly only understand some bits here and there). However before that I think I really need to clarify (for my own sake) about what exactly is going wrong in my implementation or else I won't understand why the methods suggested by you guys work. Obviously you and more have explained what's going on but I feel slightly shaky about it still. I'll try to simplify my explanation.
I have a callback function called 'display' which is where I draw all my stuff (I assume THIS is what is usually referred to as my game logic, please correct me if I'm wrong about it here). Now normally this function, due to the nature of GLUT, would only be called during certain events (resizing the window, minimizing it etc.) but what I've done is to put a command at the very end of the function which will call the function again. This essentially means that I'm calling the display function X number of times per second, in other words X frames are being drawn each second. Since I have VSYNC turned on the value of X ends up being locked down to something around 60 (assume 60 for simplicity). So 60 frames per second are drawn, 60 FPS. So far so fine, nothing special here.
Now I have a callback function which is called whenever a keyboard button I've specified is pushed. In my implementation this moves/rotates the camera a certain amount of (x,y,z) units. This is where the movement starts looking rough as mentioned before and it's from here I get slightly confused. What EXACTLY is happening the very moment I push the button? Is the call/registration of my keystroke put in some queue which is registered after certain amount of time? For example is this what happens (assuming we have 60 FPS):
Display //Render scene at t=0 ms
Display //Render scene at t=16 ms
Display //Render scene at t=32 ms
.....
Display //Render scene at t=1000 ms
Keystroke //Make change to my camera at t=1000+x ms
Or is something else happening? Again what exactly is happening the very moment I push it?
I'm going to explain it the way I understood it according to FLeBlanc's first reply; I push the button at t=0 but by the time this push has registered a second has elapsed (how long does it take to register a keystroke so we get the new change for the camera? is it strictly a second?). During this second 60 frames were drawn from the original viewpoint of the camera. Then after that comes whatever changes the keystroke did and the next frame is drawn from the new viewpoint of the camera. This cause a sudden change, a jump as described by FLeBlanc in the first reply, because none of the intermediate values between the original and new camera viewpoint are shown. Please correct me here if I am thinking wrong about what's going on when I push a button.
Now comes the next thing I am not quite getting. As I said in an earlier post I made three functions. Two of these are callback functions which basically modify a flag to know whether a key is being pressed or not. The third function has the code that updates my camera position/orientation and this third function is called in my display function, in other words it is called 60 times per second as well. The only thing different here is that while it is called nothing happens if no key is pressed. If I press and hold a key then once the keystroke has been registered the third function described above updates my camera position/orientation EACH frame by (x,y,z) units. Since I am ALSO drawing the updated camera each frame that is the reason to why I get a smooth motion. I know this would be frame-dependent but disregard that for a moment. Am I thinking correct of this? Please confirm this as well
This is quite a long post and I apologize about that but I really feel that if I can't properly grasp the problem I'm having then I'm just going to have a harder time understanding why a suggested method works.
#14 Moderators - Reputation: 5021
Posted 03 May 2012 - 11:24 PM
Also, typically, your display function, where you "draw your stuff" is not what is meant by your game logic. Game logic is all the moving of bullets, the walking of enemies, the physics updates, etc.... A function called display should be just that: something that draws things.
In the fixed timestep loops that FLeblanc and others are talking about, you will allow display to be called in the outer, or main, part of the loop. You can see that in the above posted loop code as the function render. All it does is draw the scene, using the interpolated transformation state, as many times per second as available time will allow once the logic updating is done. The actual physics and logic updating, though, take place in the function integrate which is nested inside an inner loop that is there to make sure that physics updates take priority over screen rendering if and only iff the simulation "falls behind". Otherwise, the advancement of accumulator and associated conditionals exist merely to ensure that integrate is called in a timely and precise fashion.
Now, it is important to note that Glut implements its own internal loop, implemented in glutMainLoop. If you wish to continue using Glut, you would be well advised to seek out FAQs or other sources of info specific to Glut, since you would need to work within Glut's own loop and framework of callbacks in order to implement the presented fixed time stepping, perhaps doing something like using glutTimerFunc to time the calling of your physics and logic update, and using glutIdleFunc to call glutPostRedisplay. Note that I don't use Glut anymore and never used it for any serious projects even when I did use it, so this might be completely wrong information.
(by the way, mentioning that you were using Glut would have helped immensely, and probably would have led to a solution much sooner; something like that would be considered important information for us to know.)
Edited by JTippetts, 03 May 2012 - 11:29 PM.
#15 Members - Reputation: 146
Posted 07 May 2012 - 03:34 PM
As you and the rest of the people in this thread knows I only want to control my camera but I want it to be frame-independent. I just want to move the camera which is quite simple compared to having performing some kind of more advanced physical simulation. So I thought of it and...wouldn't it be enough to actually calculate the time it takes from updating one frame to the next and take that time and multiply it by some camera speed? I do understand that this is not the best solution because this could give us different deltatimes for each frame update and generally a physical simulation might not accept a wide range of dt's. What you want instead in that case is a fixed dt to make things easier and more predictable for the physical aspects of the prorgram.
But my purpose is rather simple, only moving/rotating the camera from pos/orient. 1 to pos/orient. 2 and no more. What I thought I could do is what I did earlier, make a call to my function which controls the game logic for each frame. If a key is pressed this function will perform some update and this update would be based on the difference in time between two frames. Basically the code would be something like this:
cameraPos.x = cameraPos.x + (rotMat[0].x*moveSpeed*timeInterval); cameraPos.y = cameraPos.y + (rotMat[1].x*moveSpeed*timeInterval); cameraPos.z = cameraPos.z + (rotMat[2].x*moveSpeed*timeInterval);
where timeInterval = currentTime - prevTime and moveSpeed is some camera speed. rotMat[0/1/2] is to describe in what direction to move the camera by using the camera's local axes. Wouldn't this be sufficient for my purpose?
#16 Moderators - Reputation: 5021
Posted 07 May 2012 - 05:45 PM
#17 Members - Reputation: 146
Posted 07 May 2012 - 06:53 PM
Yes this is what I suspected as well. I just tried implementing the code I posted in my previous reply. I tried it out with enabling/disabling Vsync and it seems to work fine. One thing I've been wondering after looking through several resources is that people say dt*speed is enough for a frame-independent movement (well in case of a simple purpose). My corresponding code, from what I understand, would simply be this (assuming one coordinate to keep this short):For simple camera motion, it is fine to just use dt*speed, rather than a fixed step. Where a fixed step really becomes important is a) physics simulations, that can become unstable with high values of dt, and b) multiplayer, where slight mathematical error occurring on each machine can cause a de-sync.
cameraPos.x = cameraPos.x + (rotMat[0].x*timeInterval);
rotMat[0].x would be the actual displacement while timeInterval is the time it takes to draw one frame as mentioned before. But running it like that was way too slow so I had to add in a weighting factor (moveSpeed in prev reply). Am I understanding something wrong here?
I suspect that what might be going wrong in your code is key repeat. It's been years and years since I bothered with Glut, but if I remember correctly by default it repeats keys. This means that it will send a sequence of keydown/keyup callbacks timed according to the timing of the system key repeat rate. Typically with games, you want to use glutIgnoreKeyRepeat so that keys won't be repeated and the callback for glutKeyboardFunc will store state for the key in a table. The callback for glutKeyboardUpFunc will clear that state. Then, in your update function, you query the state of this key table and move the camera accordingly.
I checked some sources and read about this as well. Supposedly in glut (or freeglut to be more precise) you press the button and keep holding it you should have a repeating pattern; your keydown/keyup callbacks are repeatedly called as you wrote. Out of curiosity I wanted to confirm this so I simply placed code for printing out a message in both callback methods to see if those messages would be shown repeatedly as I held some defined key down. The result was that the keydown callback was called repeatedly, the keyup callback wasn't called until I released the button. Quite different from what I expected and have read about the callback methods repeating. Well as you said you haven't used GLUT for years so you might not care for this but I thought it was interesting to note anyway
Edited by Suen, 07 May 2012 - 06:54 PM.






