Lerp in Unity Explained: Smooth, Framerate-Independent Movement

Illustration of interpolation between two points with an easing curve

Mathf.Lerp and Vector3.Lerp are among the most used and most misused functions in Unity. Almost every project has a line like transform.position = Vector3.Lerp(transform.position, target, 0.1f), and almost every one of those behaves differently at 30 fps and 144 fps. This guide explains what Lerp in Unity actually does, the two correct ways to use it, framerate-independent smoothing, and when MoveTowards, SmoothDamp or Slerp is the better tool.

Watch it in action: the same Lerp at 30, 60 and 144 fps, and the fixes (4:42).


What Lerp Actually Does

Lerp means linear interpolation. Given a start value a, an end value b and a fraction t, it returns the value t of the way from a to b:

Mathf.Lerp(a, b, t) == a + (b - a) * t

Mathf.Lerp(0, 10, 0f)   // 0
Mathf.Lerp(0, 10, 0.5f) // 5
Mathf.Lerp(0, 10, 1f)   // 10

That’s all. Lerp has no idea about time, speed or frames. t is clamped to 0–1 (use LerpUnclamped to go beyond). Everything confusing about Lerp comes from what you pass as a and t.

A cube placed by Mathf.Lerp between a start and an end marker in Unity, with the current t value shown next to it.
Lerp only returns the point a fraction t of the way from a to b; here t sweeps from 0 to 1 and back.

Pattern 1: Timed Lerp From A to B

This is the “textbook” use: move from a fixed start to a fixed end over a set duration. Store the start, advance t by time, and you arrive exactly on time:

using System.Collections;
using UnityEngine;

public class MoveOverTime : MonoBehaviour
{
    public IEnumerator MoveTo(Vector3 end, float duration)
    {
        Vector3 start = transform.position;
        float elapsed = 0f;

        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = Mathf.Clamp01(elapsed / duration);
            transform.position = Vector3.Lerp(start, end, t);
            yield return null;
        }
        transform.position = end;
    }
}

Call it with StartCoroutine(MoveTo(target, 0.5f)). The movement is linear: constant speed, a hard start and a hard stop. It’s framerate-independent because t is derived from elapsed time. You can write the same thing with async Awaitable methods (see Awaitable vs coroutines).

Two cubes moved by a timed Lerp in Unity at 30 and 144 frames per second, both reaching the target in 1.5 seconds.
A timed Lerp (elapsed / duration) arrives at the same moment at 30 and 144 fps.

Pattern 2: Smoothing Toward a Target

The other common pattern feeds the current value back in as a:

void Update()
{
    transform.position = Vector3.Lerp(transform.position, target.position, 0.1f);
}

Each frame it moves 10% of the remaining distance. Big steps when far, tiny steps when close: a nice ease-out that’s great for cameras and UI. Strictly, it never fully arrives (10% of the remainder always leaves something), though floating-point precision gets there eventually.

The problem is that the constant 0.1f is applied per frame. At 144 fps the object closes the gap far faster than at 30 fps. Writing 0.1f * Time.deltaTime doesn’t fix it properly. It’s closer, but still not mathematically correct, and it changes the feel completely.

Framerate-Independent Smoothing

The correct form uses an exponential decay. Pick a sharpness (how quickly the gap shrinks) and compute t like this:

[SerializeField] float sharpness = 10f;

void Update()
{
    float t = 1f - Mathf.Exp(-sharpness * Time.deltaTime);
    transform.position = Vector3.Lerp(transform.position, target.position, t);
}

Now the motion is identical at any frame rate. A sharpness of 10 closes about 63% of the gap every 0.1 seconds. Higher is snappier. If you’d rather reason in “half-life” (the time to close half the gap), use t = 1 - Mathf.Pow(0.5f, Time.deltaTime / halfLife).

Code Framerate-independent? Use it?
Lerp(current, target, 0.1f) No Avoid in Update
Lerp(current, target, speed * Time.deltaTime) Roughly Acceptable for prototypes
Lerp(current, target, 1 - Exp(-k * dt)) Yes Yes
Lerp(start, end, elapsed / duration) Yes Yes (timed moves)
Unity cubes at 30, 60 and 144 fps: with Lerp(current, target, 0.1f) they reach the target in 1.47, 0.73 and 0.31 seconds, with the exponential form all take 0.47 seconds.
A constant t per frame (top) arrives at different times at each frame rate; 1 – Exp(-k * dt) (bottom) is identical at all three.

Easing Curves

Timed Lerps are linear by default. Reshape t before passing it in to get easing:

float t = elapsed / duration;
float smooth = Mathf.SmoothStep(0f, 1f, t);   // ease in and out
float easeOut = 1f - (1f - t) * (1f - t);      // quadratic ease out
float easeIn = t * t;                           // quadratic ease in

transform.position = Vector3.Lerp(start, end, smooth);

For designer-friendly control, expose an AnimationCurve and use Vector3.LerpUnclamped(start, end, curve.Evaluate(t)). Unclamped allows curves that overshoot for a bouncy feel. If you’re writing a lot of these, a tween library is easier, and our Asset Store tools roundup covers the popular ones.

Four Unity cubes moving over the same 1.5 seconds with linear, SmoothStep, ease-out and ease-in curves applied to t.
Same start, end and duration; only the shape of t changes: linear, SmoothStep, ease out and ease in.

MoveTowards, SmoothDamp and Slerp

  • Vector3.MoveTowards(current, target, maxStep) moves at a constant speed and arrives exactly. Use speed * Time.deltaTime as the step. Best for projectiles, platforms and enemies walking to a point.
  • Vector3.SmoothDamp(current, target, ref velocity, smoothTime) is a critically damped spring. It accelerates and decelerates naturally, is framerate-independent and handles moving targets well. It’s the best default for camera follow.
  • Vector3.Slerp interpolates along an arc, keeping magnitude. Useful for direction vectors. For rotations use Quaternion.Slerp or Quaternion.RotateTowards.
Vector3 velocity;   // must be a field, not a local

void LateUpdate()
{
    transform.position = Vector3.SmoothDamp(transform.position, target.position, ref velocity, 0.25f);
}

📚 Want the maths behind it?

Interpolation, vectors and easing all come from a handful of maths ideas that pay off for years. Our game-dev book list includes some very readable maths picks.

Browse our recommended books →

Three Unity cubes chasing the same target with MoveTowards at constant speed, exponential Lerp smoothing and SmoothDamp.
MoveTowards moves at constant speed and stops exactly, Lerp smoothing starts fast and creeps in, SmoothDamp eases in and out.

Lerping Colours, Rotations and Floats

// Fade a colour
spriteRenderer.color = Color.Lerp(Color.white, Color.red, t);

// Rotate smoothly toward a target rotation
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, 1f - Mathf.Exp(-8f * Time.deltaTime));

// Angles: use LerpAngle so 350 -> 10 goes the short way
float yaw = Mathf.LerpAngle(currentYaw, targetYaw, t);

// Get t back from a value (remap)
float t01 = Mathf.InverseLerp(minHeight, maxHeight, height);

InverseLerp is underrated: it turns any value into a 0–1 fraction, perfect for health bars, audio by distance, and colour by height.

Troubleshooting

The object never reaches the target

You’re using the smoothing pattern, which approaches forever. Snap when close (if ((target - pos).sqrMagnitude < 0.0001f) pos = target;), or use MoveTowards or a timed Lerp.

The Lerp jumps instantly to the end

t is reaching 1 immediately. You probably wrote Lerp(a, b, Time.time) (Time.time is large after the first second), or used a big speed. Compute t from elapsed time divided by duration.

Movement is faster on high refresh-rate monitors

A constant t per frame. Switch to the exponential form shown above.

Lerp inside a coroutine doesn’t move

The loop is missing yield return null, so it all runs in one frame, or the coroutine was started on an inactive object.

Camera follow with Lerp jitters

Move the camera in LateUpdate, and if the target is a Rigidbody, enable interpolation on it. See fixing Rigidbody jitter.

Two Unity cubes: plain Lerp smoothing stuck 0.0000019 m from the target, and the same Lerp with a snap that reads gap 0, arrived.
The smoothing pattern stalls a tiny distance short (top); snapping when the squared distance is below 0.0001 arrives (bottom).

FAQ

What does Lerp stand for?

Linear interpolation: finding a value a given fraction of the way between two others.

Is Vector3.Lerp framerate-independent?

Lerp itself is just maths. It’s framerate-independent when t comes from elapsed time or from the exponential formula, and frame-dependent when you pass a constant each frame.

Lerp vs MoveTowards: which should I use?

MoveTowards for constant speed with an exact arrival. Lerp for timed moves with easing or smooth ease-out following.

Lerp vs SmoothDamp for a camera?

SmoothDamp usually feels better because it eases in as well as out and copes with a moving target without lag spikes.

What’s the difference between Lerp and Slerp?

Lerp goes in a straight line. Slerp goes along an arc at constant angular speed, which is what you want for directions and rotations.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *