Framerate-Independent Smoothing

Broken Methods: a = lerp(a, b, constant)

This demo represents the result of both naive approaches. The movement is jerky and inconsistent.

Correct Method: 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:

Rory Driscoll (CodeItNow)

Provided an excellent breakdown of the exponential decay formula, showing how it can be expressed with either a smoothing factor (Pow) or a decay constant (Exp).

return Mathf.Lerp(a, b, 1 - Mathf.Exp(-lambda * dt))

Ashley (Construct)

Gave a clear mathematical proof of why common approximations fail and demonstrated that 1 - f ^ dt is the perfectly accurate solution.

So we can simplify and see that the remaining distance after one second is f^1 = f. So it's covered the same distance in one second regardless of the framerate!

Freya Holmér

Brilliantly parameterized the formula using a "half-life" (λ), creating the most intuitive way to tune the smoothing for designers and artists with the formula a = lerp(a, b, 1 - 2-dt/halfLife).

...in case you want to port your existing lerps, you can convert from the old framerate dependent "lerp factor" to a half-life value... this will match the decay rate of your old implementation at the specific framerate, in any framerate.

Unity C# Implementations

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.cs

Half-Life (Freya Holmér)

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

Smoothing Factor (Ashley / Construct)

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

Decay Constant (Rory Driscoll)

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