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:
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.csHalf-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));
}