Advertisement

Jitter in a “Soft” Follow-Cam

Started by August 07, 2021 05:11 PM
22 comments, last by Thaumaturge 3 years, 2 months ago

I’m attempting to implement a camera that follows a target (the player), and specifically one that is not fixed absolutely to the target, but rather that follows the target’s position smoothly over time.

To some degree I have this working. However, I keep encountering terrible jitter when the camera is near to the target, and perhaps at relatively-low frame-rates (~45fps). And thus far I have not managed to entirely get rid of it. (At least not without incurring some other issue.)

I’ve tried a variety of things–rearranging the order of operations (which did help a bit, I think), clamping the camera’s movement so that it doesn’t overshoot, re-working it to use a velocity-vector, or re-working it to draw from a set of samples over multiple rather than a single value. Thus far to little avail, I fear.

Since the actual code in question is a little complicated (involving multiple files and classes), let me describe my current approach to this in partial pseudocode:
(I’m doing so in part off the top of my head, and so may be mistaken in some of the details. However, I believe the below to be at least broadly accurate.)

updateTask = taskMgr.add(updateMethod, "update")

def updateMethod:
    dt = globalClock.getDt()
    updatePlayer(dt)
    return task.cont

def updatePlayer(dt):
    position += velocity * dt
    updateCamera(dt)

def updateCamera(dt):
    diff = targetPos - cameraPos
    scalar = cameraSpeed * dt
    if scalar > 1:
        scalar = 1
    cameraPos += diff * scalar

Does anyone see a problem with my approach here? Or have suggestions as to something that might fix the issue that I’m seeing?

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

you may try using different dt=>

dt = (lastframedt + nowframedt) / 2.0;

anyway jittering might be caused of inprecision like dt is 0.001 and you musltiply that by some velocity vector that when is close to object has value of 0.0000003, and you get wierdo results swinging through -0.000000x to 0.000000x

you need to check if values aren't too small, can't see actually what happens and what the values are

Advertisement

Hmm… Now that I didn't think of! Thank you for the suggestion!

Trying it, it seems like it helps a little--but the jitter remains.

Prompted by the idea, I also tried keeping a list of dt-values and taking their average, which again seems to help a little, but not hugely.

This leads me to think, at least, that the issue likely does not lie in spikes in my delta-time.

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

Try this:

diff = (targetPos - cameraPos) * 0.3f;

Should work if your timestep is fairly consistent. Otherwise i would subdivide the step.

JoeJ said:
diff = (targetPos - cameraPos) * 0.3f;

That will likely work--but it will also make the camera follow well behind the player, I fear--especially at lower frame-rates. And that I've found to cause problems in both gameplay and the diegetic UI, alas.

(I'm using a variable time-step.)

Sub-dividing the time-step might work, however… The logic involved isn't all that heavy, so it shouldn't greatly affect the frame-rate.

Giving it a shot, I'm surprised to report that it doesn't seem to help: the camera jitters just as before, even if I carve up the dt so finely that it routinely gets 14 to 16 iterations. o_0

I feel like I'm surely doing something wrong somewhere to be having so much trouble with the camera--but I don't know where! :/

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

I use a spring/damper equation and it works pretty well. Here's an old code snippet (hopefully I can remember what everything is , LOL)

   // Calculate asimuth delta 
   // For the left-handed rotation from Va to Vb: atan2(Vn . (Vb x Va), Vb . Va)
   double fADelta = atan2(m_clTarget.clUp.DotProduct(m_clTAzimuth.CrossProduct(m_clCAzimuth)),m_clTAzimuth.DotProduct(m_clCAzimuth));
   // Calculate asimuth acceleration
   double fAAccel = (-m_fASpringK * fADelta) - (m_fADampingK * m_fAVelocity);

   // Calculate new asimuth velocity
   m_fAVelocity  += fAAccel * pRenderData->m_fDelta;
   // Calculate new asimuth and right vectors
   double fADist = m_fAVelocity * pRenderData->m_fDelta;

   CDLDblQuaternion clARot(m_clTarget.clUp,fADist,EDLQuatAxisRotate::Yes);
   // Build rotation around up vector for asimuth distance 
   // Calculate new asimuth
   m_clCAzimuth = clARot.Rotate(m_clCAzimuth);
   // Calculate new right
   m_clCRight = m_clCAzimuth.CrossProduct(m_clTarget.clUp);

This is for following a target (character or whatever). This piece just does the camera rotation around the Up vector of the target. The target has Up, Face and Right vectors for convenience. We deal with the elevation of the camera and distance separately but you can do the same sort of thing if you want springs on everything. The full routine actually lets the target move in 3D so it will work with Airplanes and stuff like that.

m_clTarget == Target we are following with clUp, clFace and clRight vectors
m_clTAsimuth == is a vector pointing the opposite of the clFace vector of the target, kind of back towards the camera
m_clTAsimuth == is a vector from the target to the camera projected down into the Face/Right plane of the target (i.e. we ignore the elevation here)
m_fAVelocity == how fast the camera is rotating in radians per second

m_fASpringK == 2 * PI (but you can change this to get different behavior)
m_fADampingK == 2.0 * sqrt(m-FASpringK (this is typical but again you can play with this)

pRenderData->m_fDelta == change in time in seconds from last time step
fADist == angle you moved for this time step.
clARot == Quaternion we use to rotate stuff by fADist around our target's Up vector
m_clCAzimuth == Azimuth vector of camera after our calculation
m_clRight == Right vector of our camera for orientation

I hope I got all that right.

Here is the result but you can ignore most of the video since it's kind of boring.

https://www.youtube.com/watch?v=1fXVoDrktR0




Advertisement

Hmm… If I understand correctly, you're only applying the spring-effect to your rotations, not to your positions, correct? That's pretty much the opposite of what I'm doing: I (want to) have the camera softly following in position, while matching direction exactly.

That said, looking at your video it seems to work quite well. And the logic should be just as applicable to position as to rotation, I would think.

So, I think that I'll give that a closer look when I'm less tired--it's now pretty late here. Thank you! ^_^

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

Thaumaturge said:

Hmm… If I understand correctly, you're only applying the spring-effect to your rotations, not to your positions, correct? That's pretty much the opposite of what I'm doing: I (want to) have the camera softly following in position, while matching direction exactly.

That said, looking at your video it seems to work quite well. And the logic should be just as applicable to position as to rotation, I would think.

So, I think that I'll give that a closer look when I'm less tired--it's now pretty late here. Thank you! ^_^

You can do the same thing with distance, in fact it's probably easier. The key part is this:

double fAAccel = (-m_fASpringK * fADelta) - (m_fADampingK * m_fAVelocity);

Just change things from radians to distance.

Thaumaturge said:
That will likely work--but it will also make the camera follow well behind the player, I fear--especially at lower frame-rates. And that I've found to cause problems in both gameplay and the diegetic UI, alas.

Yeah, but that's solvable, e.g. by modulating the 0.3 depending on distance.

I have used such things successfully in a variable time step game. I did so naively by subdividing the timestep for affected functions so it becomes almost a fixed timestep. Then you can tweak your settings until feels good and it works consistently even if the whole timestep differs wildly.

It's also possible to solve the control problem precisely analytically, with the objective to drive both distance and velocity to zero at some future point in time.
In your snippet for example, you may set distance to zero, but velocity could be high, so you would overshoot in the next step if it were a physics simulation.

Gnollrunner said:
You can do the same thing with distance, in fact it's probably easier. The key part is this:

Noted, and thank you! ^_^

JoeJ said:
Yeah, but that's solvable, e.g. by modulating the 0.3 depending on distance.

I mean, that's pretty much what I'm doing by multiplying by the difference between the target and the current position: when the difference is great, the resulting multiplier will be great, and when it's small, the multiplier will be small.

JoeJ said:
I have used such things successfully in a variable time step game. I did so naively by subdividing the timestep for affected functions so it becomes almost a fixed timestep. Then you can tweak your settings until feels good and it works consistently even if the whole timestep differs wildly.

As I noted above, it doesn't seem to be working in my case. :/

JoeJ said:
In your snippet for example, you may set distance to zero, but velocity could be high, so you would overshoot in the next step if it were a physics simulation.

In all fairness, my snippet above doesn't use a velocity-based approach, so there should be no overshooting the target--at least as far as I see.

JoeJ said:
It's also possible to solve the control problem precisely analytically, with the objective to drive both distance and velocity to zero at some future point in time.

I do hope that it doesn't come to such a thing, however. ^^;

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

This topic is closed to new replies.

Advertisement