My favourite animation trick: exponential smoothing

There's a certain simple animation thing that I've been using almost since I've ever started doing anything related to graphics. I use it for rotating & moving the camera, for moving figures in a turn-based game, for moving UI elements, for smoothing volume changes in my audio lib, everywhere! So I decided I'll write about it. The trick itself is nothing new, – in fact, you've probably already heard about or even used it, – but I'll also show it in some examples and explain how it works mathematically :)

Speaking of UI, say you're making some UI component, maybe a toggle button. Something like this (click it!):

This simply computes the position of the switch as a function of its state:

`position.x = turned_on ? max_x : min_x;`

This works perfectly, but feels a bit lifeless. Adding some *animation* to it would be cool! Animations are not just a fancy visual thing, they help the user understand what's going on. Instead of teleporting the toggle indicator to its new position, let's move it smoothly:

The downside is that we need to run some updating animation now:

```
position.x += (turned_on ? 1 : -1) * speed * dt;
position.x = clamp(position.x, min_x, max_x);
```

However, this still looks a bit clumsy due to having a constant speed (i.e. the position is a *linear* function of time). Let's add some easing function on top of that, like the classic cubic \( 3t^2-2t^3 \):

or a square root \( \sqrt t \):

The difference between these may be hard to see, so let's slow down the animation by a factor of 8:

Linear: | ||

Cubic: | ||

Square root: |

This time, instead of just updating the switch position, we have to keep track of some extra animation state:

```
t += (turned_on ? 1 : -1) * speed * dt;
t = clamp(t, 0, 1);
ease = (3 * t * t - 2 * t * t * t);
position.x = lerp(min_x, max_x, ease);
```

Here, I'm using the fact that `smoothstep`

is symmetric in the following sense: `1 - f(t) = f(1 - t)`

, meaning the forward and backward animations can use the same code. With `sqrt`

things are a bit different: we have to explicitly use a different easing function depending on the animation's direction:

`ease = turned_on ? sqrt(t) : 1 - sqrt(1 - t);`

Whichever looks best is arguably a matter of taste, but of all these `sqrt`

is my favourite: the switch starts moving really fast (this is because `sqrt`

has infinite derivative at zero), but then slows down nicely as it reaches the destination (the cubic one is my second favourite, though). The downside of this version is that we need quite a lot of bookkeeping even in the simplest possible case of a two-state toggle button (later in the article I'll show how this becomes a nightmare in more complicated scenarios). Another downside is that it has a discontinuity: it jumps suddenly if the user clicks on it the middle of animation (try it!).

Thankfully there's a similar version which uses the minimal possible state and doesn't have the "jumping" problem:

I call it *exponential smoothing* (for reasons that will become clear later). I've also heard it being called *approach*, and I'm certain it has it's own name in every engine. Here it is slowed down 8x and compared to `sqrt`

:

Square root: | ||

Exponential: |

Here's the code for the exponential version:

```
target = (state.value ? max_x : min_x);
position.x += (target - position.x) * (1 - exp(- dt * speed));
```

Intuitively, on each frame we nudge the current position towards its *target position* (which is determind by the on/off state). However, the amount of nudging `(1 - exp(- dt * speed))`

looks really weird, doesn't it? Before we see where it comes from, let's have a look at some more complicated animations.

Say we have some kind of map, and a camera scrolling/moving around.

*Yes I've made a whole procedural map generator & renderer just for this example, and I have zero regrets.*

Again, this begs us to add some animation. Let's interpolate it with constant speed:

Here's the code:

```
position.x += sign(target.x - position.x) * speed * dt;
position.y += sign(target.y - position.y) * speed * dt;
```

See this jittering after the animation completes? That's because `target.x - position.x`

keeps alternating between being positive and negative. Instead of `sign(delta)`

we need some function that clamps the delta:

```
float update(float & value, float target, float max_delta)
{
float delta = target - value;
delta = min(delta, max_delta);
delta = max(delta, -max_delta);
value += delta;
}
update(position.x, target.x, speed * dt);
update(position.y, target.y, speed * dt);
```

Quite a mouthful for such a simple thing! And here's the result:

Much better, although it's still a bit clumsy and also weird if we move the camera faster than the animation completes. We could, as before, add some easing function, like the cubic one:

although this time it gets really complicated: we have to maintain a queue of requested movement events, and animate them one by one (otherwise I have no idea how to slap the easing function here). This still looks a bit weird when moving the camera fast enough. We could just ignore user's input while the animation is active, but this is a deadly sin as it is infuriatingly frustrating from the user's perspective.

The perfect solution? Why, exponential smoothing of course! The code barely changes compared to the toggle button example:

```
position.x += (target.x - position.x) * (1.0 - exp(- speed * dt));
position.y += (target.y - position.y) * (1.0 - exp(- speed * dt));
```

and here's how it looks like:

Pretty nice, if you ask me! Notice how it speeds up naturally if you click fast enough.

Ok, so what's up with this `1 - exp(- speed * dt)`

, what on Earth is that?

Let's start with a simplified version: we have some animation, it has a current `position`

and the new position `target`

which it must move towards with some `speed`

. To make the movement faster when the difference between `position`

and `target`

is large, we make the speed proportional to this difference:

`position += (target - position) * speed * dt;`

Notice how it doesn't require maintaining *any* state other than the current and the target position! (`speed`

is usually a constant.) It even doesn't need to keep track of time that elapsed since the start of the animation, and it adjusts automatically if the `target`

suddenly changes.

Now this already works perfectly in many situations, but there's a small catch. Here's the toggle button again, with the above udpate code:

See the jittering? That's because I've set the `speed`

value so high that `speed * dt`

became larger than 1! Specifically, I used `speed = 220`

and `dt = 1 / 125`

.

To understand what's happening, it is useful to rewrite the code above using `lerp`

:

`position = lerp(position, target, speed * dt);`

You can check that this is ultimately the same formula. We can clearly see what's going on: the formula interpolates between the current value and the target value. The closer the interpolation parameter `speed * dt`

to zero, the slower the interpolation. The closer it is to one, the faster the movement.

Now, what happens when `speed * dt`

is larger than 1 is that the interpolation *overshoots*! The only reason it still works is that `speed * dt`

is less than 2, so that the *absolute* delta between `position`

and `target`

still decreases with time. Here's an example with `speed * dt = 248 / 125 < 2`

:

and here's one with `speed * dt = 252 / 125 > 2`

:

The last one doesn't do anything useful at all.

To solve this, we could simply clamp the value by 1:

`position = lerp(position, target, min(1, speed * dt));`

However, this doesn't seem like the right thing to do in all scenarios. Consider why `speed * dt`

might actually happen to be so large?

One reason is that your `speed`

value is too large because you want a really quick animation. However, as we've seen with the above toggle buttons, this is actually way too quick for any reasonable user – the actual animation is impossible to notice. So, our `speed`

value is usually not that high.

The other reason is that `dt`

is too large. Maybe because your code runs too slow, and your framerate is dropping. Maybe because the user moved to a different tab/window and your code was sleeping, and now it got woken up with a `dt`

of many seconds.

When applying such a `dt`

to something like physics, you certainly want to clamp it, or subdivide into several updates, etc. With animations, however, wouldn't it be cool if everything worked perfectly even in this case? Even if your physics might lag, at least the camera & buttons would still work nicely – as a user, I would really appreciate such care.

Ok, we want to solve the problem, but how? Here's the two-step recipe:

- Realize that what we're doing is numerically solving a certain differential equation
- Solve the equation symbolically and use the result directly

Time-dependent update that works for small `dt`

but breaks for large `dt`

is pretty typical for numerical solvers of differential equations. What equation does `position += (target - position) * speed * dt`

solve? Whenever you see `A += B * dt`

, this corresponds to an equation
\[ \frac{d}{dt}A=B \]
In our case, the equation is
\[ \frac{d}{dt}\text{position} = (\text{target} - \text{position})\cdot\text{speed} \]

I will die if I keep typing these formulas with all words spelled out, so let's make a few variable changes: call \( x = \text{position} \), \( a = \text{target} \), and \( c = \text{speed} \):

\[ \frac{d}{dt}x = (a-x)\cdot c \] Solving this needs just a few tricks: \[ \frac{d}{dt}(x-a) = \frac{d}{dt}x = (a-x)\cdot c = -(x-a)\cdot c \] \[ x-a = (x_0-a)\cdot\exp{-c\cdot t} \] \[ x = a - (a-x_0)\cdot\exp{-c\cdot t} \] \[ x = x_0 + (a-x_0)\cdot\left(1-\exp{-c\cdot t}\right) \]*Btw, a similar exponent appears in e.g. volumetric rendering for pretty similar reasons.*

It's not important to understand exactly where this all comes from. The point is that if we believe that `position += (target - position) * speed * dt`

is the right formula for *small* `dt`

, then the formula `position += (target - position) * (1 - exp(- speed * dt))`

is the right formula to use for *any* `dt`

. This is further supported by expanding the latter equation in terms of Taylor series for the exponent: \( \exp(x) \approx 1 + x \), so that

i.e. we get exactly the former equation.

The cool thing is that it doesn't care about old values: if you have your previous value \( x_0 \) and you know how much time has passed between the previous and the current iteration, you can compute the new value. (This is a direct consequence of being a first-order differential equation.)

So, the TL;DR is that

`position += (target - position) * (1 - exp(- speed * dt))`

is the right formula that works for any `speed`

and `dt`

. Even if the product `speed * dt`

is too large, `exp(- speed * dt)`

handles it nicely, since `exp`

of a large negative number is just something close to zero, so `1 - exp`

will be close to one.

We can, as before, rewrite this using `lerp`

: `position = lerp(position, target, 1 - exp(- speed * dt))`

or even `position = lerp(target, position, exp(- speed * dt))`

. There are many ways to rewrite this equtaion.

Usually, we think of animation in terms of its *duration*. Like, the toggle button should move to the new place in 0.125 seconds (the actual value used in the examples in the beginning of the post), after that it stops moving. With this exponential formula, however, the animation technically takes *infinite* time to complete! `exp(- speed * time)`

gets smaller with time, but it *never* equals zero, so that `position`

technically *never* equals `target`

(provided they were different to start with).

However, in practice we have a ton of limitations. If `position`

is a floating-point number, it quickly reaches the precision limit, and it becomes equal to `target`

in practice. If it is, say, the camera position, the user probably won't notice that the animation is still going since the delta `target - position`

gets ridiculously small even before it hits floating-point precision limits.

So, what does the `speed`

parameter mean, exactly? It means the following: `1 / speed`

is the time in which `position`

becomes closer to `target`

by a factor of `e = 2.71828...`

exactly. Do whatever you want with this information.

I usually set the `speed`

to something in the range 5..50. For a linear/cubic animation of a certain `speed`

, I usually set the exponential version speed to be `2 * speed`

, this feels about right (again, this is what was used in the examples above).

If you google "exponential smoothing" (or "exponential moving average"), you might find the wiki article on something completely unrelated which, nevertheless, features some pretty similar formulas. It actually is the discrete analogue of what we were talking about in this post!

Suppose that our `dt`

is always the same; also suppose that `target`

changes as often as every iteration. Then, indexing the values with the iteration number, we compute something like `position[i] = (target[i] - position[i - 1]) * factor`

, where `factor = 1 - exp(- speed * dt)`

. In this case, one typically sets `factor`

directly to some value between 0 and 1 instead of deriving it from other values (although the aforementioned wiki article does explain what this `factor`

actually means).

People use it in signal processing for the same reasons I do for animations: it doesn't require maintaining previous values or any other obscure state, just the current averaged value. They also use it in digital audio, where you typically have a fixed `dt`

of `1 / freq`

the inverse sampling frequency (e.g. `1/44100`

or `1/48000`

).

I had the idea of this post for many months, glad to finally get it done :)

As always, thanks for reading!