a = lerp(a, b, constant)This demo represents the result of both naive approaches. The movement is jerky and inconsistent.
a = lerp(a, b, 1 - 2-dt/halfLife)Using true exponential decay, the motion is smooth and perfectly synchronized across all framerates.
In game development, a common task is to smoothly move a value, like a camera's position, towards a target. A frequent but incorrect approach is to use linear interpolation (lerp) inside an update loop.
The First Mistake: A naive implementation looks like a = lerp(a, b, 0.05). Since the update loop runs every frame, this constant factor of 0.05 is applied more often at higher framerates, causing the motion to be visibly faster. This is framerate-dependent and the core problem visualized above.
The Second Mistake: To fix this, developers introduce delta time (the time since the last frame, e.g., Unity's Time.deltaTime). They try a = lerp(a, b, smoothingFactor * deltaTime). While this seems logical, it's not mathematically sound. The result is only a crude approximation and, more dangerously, if smoothingFactor * deltaTime ever exceeds 1.0 (e.g., during a lag spike), the value will overshoot the target, causing jittery, unstable behavior.
The Solution: FRIM Lerp. The correct method relies on a principle of exponential decay, which guarantees the value converges at the same rate over real time. This approach is often called FRIM Lerp (Framerate-Independent Motion using Lerp). The core idea is to calculate the 't' parameter for the lerp function with 1 - kdeltaTime. This page synthesizes the solutions presented by three key figures who have clarified this concept:
Here are three ready-to-use C# extension methods for Unity, including versions for `Vector3`. Create a new static class (e.g., `SmoothingExtensions.cs`) and paste this code inside it to use these methods on any `float` or `Vector3` value.
Download SmoothingExtensions.csThe most intuitive method. Tune the smoothing by answering: "How many seconds should it take to get halfway to the target?" A smaller half-life means faster smoothing.
// Usage: currentValue = currentValue.LerpFrimHalfLife(targetValue, 0.2f, Time.deltaTime);
public static float LerpFrimHalfLife(this float source, float target, float halfLife, float deltaTime)
{
return Mathf.Lerp(source, target, 1f - Mathf.Pow(2f, -deltaTime / halfLife));
}
Use a `smoothing` factor from 0 to 1. This value represents the fraction of the distance remaining after one second. A value of `0.1` means 10% remains (90% covered) after one second.
// Usage: currentValue = currentValue.LerpFrim(targetValue, 0.1f, Time.deltaTime);
public static float LerpFrim(this float source, float target, float smoothing, float deltaTime)
{
return Mathf.Lerp(source, target, 1f - Mathf.Pow(smoothing, deltaTime));
}
Uses a decay constant, `lambda`. A higher lambda results in faster smoothing. This form is common in physics and engineering.
// Usage: currentValue = currentValue.LerpFrimLambda(targetValue, 5f, Time.deltaTime);
public static float LerpFrimLambda(this float source, float target, float lambda, float deltaTime)
{
return Mathf.Lerp(source, target, 1f - Mathf.Exp(-lambda * deltaTime));
}
A demo of a bug almost every game has shipped at least once: `lerp(a, b, 0.1)` every frame is not frame-rate independent, so the same code feels different at 30, 60 and 144 frames per second. This page runs the naive version and the corrected exponential form side by side so you can see them drift apart.
One page, no install. By Tront (Trent Sterling), who hit this in production more than once.
More from Tront
All games and tools · Devlog · Discord · Source on GitHub · tront.xyz