Frame-rate independent interpolation for Unity
Published
Back in 2002, Robert Penner published his book, Robert Penner's Programming Macromedia Flash MX. The book was significant in that it unveiled a set of stand-alone "easing functions" for simple animation. You've probably seen these easing functions, they've been ported to every language and websites like easings.net are dedicated to them. These functions make it simple for programmers to apply various eases to tween-based animation. By "tween-based" I'm referring to keyframe animation: interpolation between two fixed points in time.
But what about animations that don't have a start time and an end time? Consider procedural animations that happen in a real-time environment, for example: "every frame ease a game object towards another game object". Those animations can't be keyframed; mathematically, we can't interpolate the in-between values (the 'tweens) if there is no definitive end to the animation.
The Exponential Slide
[300 300 ~/articles/decay.png]
Penner's book provided a solution for this as well, a solution he dubbed the exponential slide. Though the exponential slide has been extremely significant for my work over the years, the technique was largely overlooked -- partly because the slide is so simple and partly because most Flash developers weren't making real-time content, especially in 2002. Nowadays it seems that most call this technique damping or describe it as asymptotic easing.
Whatever you call it, the idea is dead-simple: move an object a fraction of the way towards a target. Do this once every tick and the result is an object that gracefully eases to a halt. You can visualize a keyframed example of this type of motion at easings.net/#easeOutExpo. Here's some what the exponential slide looks like, implemented in Unity:
void Update(){ // move 5% of the way to the target: transform.position += (target - transform.position) * 0.05f; }
It's common to see the exponential slide written using interpolation functions as well:
void Update(){ // move 5% of the way to the target: transform.position = Vector3.Lerp(transform.position, target, 0.05f); }
One significant problem with the exponential slide is that the calculation does not account for frame-rate. Many have tried to solve this problem, often by using a scaled delta-time as the percent value in the above equations. And while those solutions can produce better results, they definitely do not produce results that are frame-rate independent. Well, Rory Driscoll did the math and provided us with a far better solution, written succinctly as:
public static float Damp(float a, float b, float lambda, float dt) { return Mathf.Lerp(a, b, 1 - Mathf.Exp(-lambda * dt)) }
As an aside, I'll say that Rory's elegant solution reminds me quite a bit of Robert Penner's original easing functions; it's a linear interpolation with a calculated value for the percentage.
Some Shareable Code
I've extended the logic and combined it with Allen Chou's similarly amazing work with numerical springs. These functions are a perfect starting point for procedural animation. I've provided these functions in the gist below as a class called AnimMath. Here's what it looks like in use:
void Update(){ // slide towards the target value, reaching 99.9% after 1 second: transform.position = AnimMath.Slide(transform.position, target, .001f); }
Check out the gist and let me know what you think!