Conclusion
While lerp is linear by definition, running it iteratively over multiple frames results in an exponential curve.
Lerping iteratively1 from 0 to 1 with a factor \(d\) given a frame number2 \(x\) can be expressed as:
\[f(x) = 1 - (1 - d)^x\]Which looks like:
Lerp
I am working on player movement in Unity (a game engine), and want the player’s velocity to change smoothly from frame to frame3 - with no abrupt speed changes.
Naturally, I decide to use lerp to this effect:
\[lerp(a, b, d) = a + d \cdot (b - a)\]Very commonly used in game development, lerp moves a value \(a\) towards a value \(b\) by a factor \(d \in [0, 1]\). It can be used to animate an object’s position, rotation, color, and almost anything else.
Because it is so common and accessible, I want to know what the underlying animation curve is when it is used over multiple frames.
Additionally, I worry that frame rate may have an effect on that curve, and want to prevent that from happening as it can result in different gameplay for players with varying frame rates.
The Curve
If we run lerp each frame with constant \(a\) and \(b\) while increasing the factor \(d\) as a multiple of the time elapsed4, the result is a simple line that reaches the target value when \(d = 1\):
However, this is quite rarely done - more likely, we assign the result of lerp to \(a\) on each frame, and set the factor \(d\) to a multiple of the time since the last frame \(\Delta time\)5, measured in seconds.
To make things even simpler, we can set \(a = 0\) and \(b = 1\), which does not affect the underlying curve.
We can then define the iterative lerp as a series \(S\) given a frame number \(n\):
\[S_{0} = 0\] \[S_{n} = S_{n-1} + d \cdot (1 - S_{n-1})\]Each frame, we take the previous frame’s result and add to it the remaining value multiplied by \(d\).
We can simplify this expression by representing the remaining value each frame as \((1 - d)^{n-1}\) instead6:
\[S_{0} = 0\] \[S_{n} = S_{n-1} + d \cdot (1 - d)^{n-1}\]\(1 - d\) happens to be the fraction of the target value remaining after a frame passes. Raising it to the power of \(n-1\) gives us the remaining value after \(n-1\) frames have passed.
For example, with \(d = 0.1\), we would have \((1 - 0.1) = 0.9\) remaining on the first frame, \(0.9 * 0.9\) on the second, and so on.
We can use the same reasoning to replace the remaining recursive term:
\[\begin{aligned} & 1 - S_{n-1} = (1 - d)^{n-1} \\ \Leftrightarrow {}& S_{n-1} = 1 - (1 - d)^{n-1} \end{aligned}\]The expression thus becomes:
\[\begin{aligned} S_{n} &= 1 - (1 - d)^{n-1} + d \cdot (1 - d)^{n-1} \\ &= 1 - (1 - d)^{n-1} \cdot (1 - d) \\ &= 1 - (1 - d)^{n} \end{aligned}\]Now that there are no recursive terms, and since our expression matches the case \(S_{0} = 0\), we can convert the series to a continuous function directly:
\[f(x) = 1 - (1-d)^x\]We can now see that the curve underlying an iterative lerp is an exponential! Let’s see what it looks like in a plot:
You can see how different values for the factor \(d\) affect the curve by dragging the slider.
Check out part 2, Is Lerp Frame Rate Independent?, where we look at iterative lerp as a function of time (not frames) and investigate its dependence on frame rate.
Acknowledgements
“Improved Lerp Smoothing” by Scott Lembcke on Game Developer
“Frame Rate Independent Damping Using Lerp” by Rory Driscoll
-
Lerping iteratively means the result becomes the starting point for the next frame. ↩
-
Assuming a steady frame rate, frame number is equivalent to time elapsed. Making \(d\) a multiple of the time since the last frame also helps make it resilient to frame rate fluctuations, but does not make it completely independent from them. ↩
-
Games work by successively rendering frames to the player’s screen. After the game state is updated, a frame is rendered and displayed for a mere fraction of a second - until the next one is ready. ↩
-
Making \(d\) a multiple of a measure of time (total time elapsed in this case, but also \(\Delta time\) below) makes the process almost entirely frame rate independent, but not completely so. We will later look into how to make lerp perfectly independent from frame rate. ↩
-
At 60 frames per second, \(\Delta time = \frac{1}{60} = 0.01666...\), which is a very small value. We ignore the case where \(d \notin [0, 1]\), because it is rare that a frame takes long enough for it to happen and because \(d\) can simply be clamped to that range. ↩
-
My own attempts and all external sources I could find lead me to believe this approach (framing the remaining value as an exponential) is the only way to avoid a mathematical dead end. Please do mention an alternative if you know of one! ↩