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));
}
About · More by Tront

Frame-Rate Independent Lerp by Tront

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