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));
}