2
\$\begingroup\$

I often use a weighted average for smoothly animating numerical values to a target value, like so:

frame(dt) {
    value = ((value*someFactor) + targetValue) / (someFactor+1)
}

As you can see dt (delta time) is not used at all, so the result is not the same for different framerates. How can dt be applied correctly here?

\$\endgroup\$
1

2 Answers 2

3
\$\begingroup\$

This formula updates value so a constant fraction of the difference from the value and the target is removed each frame. Let alpha = 1/(someFactor+1). Then we can rewrite updating to the new value' as:

value' = (1-alpha) * value + alpha * target

Then basic algebra says

value' - target = (1-alpha) * value + alpha * target - target
value' - target = (1-alpha) * value + (alpha-1) * target
value' - target = (1-alpha) * value - (1-alpha) * target
value' - target = (1-alpha) * (value - target)

Let u be the difference between value and target, i.e. u = value - target and u' = value' - target. Thus:

u' = (1-alpha) * u

Here we have an option; I will pick the one that doesn't involve calculus. We can repeat this process:

u'' = (1-alpha) * u' = (1-alpha) (1-alpha) * u 

We can see the pattern is that after n frames, the difference from the target is (1-alpha)^n * u_initial. Let's assume that your game runs at some framerate, and you have adjusted it to your liking. Then after x seconds, n = x/dt_nominal frames will have passed, where dt_nominal is the typical dt when you adjusted it (so 16.6ms for 60fps). So now u will be (1-alpha)^(x/dt_nominal) * u_initial.

The beauty is that this formula is defined for any real x, not just ones which make the exponent a natural number. So here we can replace x=dt as the duration of a frame, and get (1-alpha)^(dt/dt_nominal) * u_initial. But, using some properties of the exponential:

(1-alpha)^(dt/dt_nominal) = e^(-dt * ln(1/(1-alpha))/dt_nominal)

So now we can write what the difference is after two frames:

u'' = exp(-dt2 * ln(1/(1-alpha))/dt_nominial)
    * exp(-dt1 * ln(1/(1-alpha))/dt_nominial) * u_initial
u'' = exp(-(dt1+dt2) * ln(1/(1-alpha))/dt_nominal)

Basically, this formula lets us calculate what the value will be at any arbitrary point in the future. We just choose our update rule to calculate the value dt seconds in the future. We also got the nice property that our solution is independent of how many frames occur or their duration, because the final value depends only on the sum of the durations, even though we calculate it individually for each frame.

So we can make our update rule:

beta = ln(1/(1-alpha))/dt_nominial 
     = ln((someFactor+1)/someFactor)/dt_nominial
amount = exp(-dt * beta)
value = (amount) * value + (1-amount)* target

Note that beta is a constant. You can figure out what beta is for your code, and going forward adjust beta directly rather than using someFactor. It turns out beta is related to the half-life: it is the 1/e-life, instead of the 1/2-life.

BONUS

If you assume your frame times are all reasonably small and so is beta, then a useful approximation is

exp(-dt*beta) ~= 1 - dt * beta

Thus your update rule is:

beta = ln(1/(1-alpha))/dt_nominial
value = (1 - dt * beta) * value + (dt * beta) * target

In the inner loop you therefore avoid a transcendental function. In UI code, use exp. If you are moving particles, use the approximation.

\$\endgroup\$
1
  • \$\begingroup\$ That was... beautiful. \$\endgroup\$
    – Nevermind
    Commented Jul 12, 2014 at 18:10
0
\$\begingroup\$

I'm assuming that you're incrementing someFactor every frame, yes?

someFactor += 0.1f;

Why not try incrementing it based on dt?

someFactor += 0.1f * dt;

This concept is reified in Jason Gregory's Game Engine Architecture under Section 7.4. if you're interested in reading into it further.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .