Framerate-Independent Smoothing

The fixed factor matches the half-life at 60 FPS. Both rows start together and repeat every 3 seconds.

Fixed factor: a = lerp(a, b, constant)

A fixed factor is applied once per simulated frame. More frames means faster convergence.

Correct Method: a = lerp(a, b, 1 - 2-dt/halfLife)

Exponential decay preserves the same convergence rate. Lower FPS still means fewer visible updates.

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). This is a useful approximation, but its convergence rate still changes with the timestep. If smoothingFactor * deltaTime exceeds 1.0 (e.g., during a lag spike), an unclamped lerp can overshoot. Unity's Mathf.Lerp clamps the factor to [0, 1], so it snaps to the target instead.

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" (h), 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.

What "independent" means

For a fixed target, the remaining error is multiplied by 2-dt/halfLife. Two steps multiply to 2-(dt1 + dt2)/halfLife, so splitting the same elapsed time into different steps gives the same answer. This is the exponential-decay property explained by Rory Driscoll and Ashley.

The rows simulate 10, 30 and 60 updates per second. Compare them at shared sample times: a 10 FPS object still moves in 100 ms steps. A changing target is sampled at different times at different rates, so exponential smoothing alone does not make those paths identical.

The half-life control sets rate = ln(2) / halfLife. The fixed factor is matched at 60 FPS; the delta-time option uses that continuous rate with a clamped Euler step. Pausing or leaving the tab pauses this demo's clock. Browser repaint frequency still limits how many updates you can actually see, as described in requestAnimationFrame's timing notes.

Use seconds consistently, a positive half-life, non-negative delta time and decay rate, and a remaining-distance factor between 0 and 1. Exponential damping approaches a fixed target asymptotically; use a small tolerance if your game needs a definite "arrived" state.

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