Can someone explain how using Time.deltaTime as 't' in a lerp actually works?

I can’t quite get why using it gives the desired results… and then at other times using Time.time works (eg in the lerp scripting reference examples Time.time is used?)

Thanks.

Time.deltaTime is the time since the last frame. In Lerp, the third parameter is a float between 0 and 1. If it’s 0, you get the first parameter in Lerp. If it’s 1, you get the second.

Therefore, with a statement like:

transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);

in Update, you get a position that’s part-way between transform.position and target.position, depending on the time difference between the current frame and the last frame.

For example, a Time.deltaTime value of .01 (100fps) would return a value that’s a combo of 99% transform.position and 1% target.position. As this is repeated each frame, the transform.position, assuming target.position is stationary, becomes closer and closer to target.position but never quite reaches it. (Unless the framerate drops to 1fps or less, in which case Time.deltaTime would be 1 or greater, so the return value would be 100% target.position.)

Note that this is a “trick” usage of Lerp, which creates a “slow down as you approach the target” effect. It’s mathematically incorrect, though, since it will produce results that vary somewhat depending on the framerate.

This is better:

Code below is not C# but JS-like, now obsolete, Unity Script.
function MoveObject (thisTransform : Transform, startPos : Vector3, endPos : Vector3, time : float) : IEnumerator {
    var i = 0.0;
    var rate = 1.0/time;
    while (i < 1.0) {
        i += Time.deltaTime * rate;
        thisTransform.position = Vector3.Lerp(startPos, endPos, i);
        yield; 
    }
}

That’s a Coroutine that moves a transform from startPos to endPos over a given time. This happens as i advances from 0.0 to 1.0, and results in completely linear movement, without the “slowdown” effect.

As for Time.time, consider:

transform.position = Vector3.Lerp(startPos, endPos, Time.time);

in Update. This moves the transform from startPos to endPos as the time advances from 0.0 to 1.0. Once the time is past one second, no further movement will occur since the third parameter in Lerp is clamped between 0 and 1. Obviously this isn’t very useful unless you actually only want movement to happen for the first second of gameplay for some reason.

Another script based on Eric’s, that prevents the jag effect. Thanks to @Eric5h5 for the original script, the idea of rate is very useful.

Code below is not C# but JS-like, now obsolete, Unity Script.
var pointA:Vector3;
var pointB:Vector3;
var time:float = 10.0;
private var i:float = 0.0;
private var rate:float = 0.0;

function Update () 
{   
    MoveObject(this.transform, pointA, pointB, time);
}

function MoveObject (thisTransform : Transform, startPos : Vector3, endPos : Vector3, time : float) 
{
    rate = 1.0/time;
    if (i < 1.0)
    {
        i += Time.deltaTime * rate;
        thisTransform.position = Vector3.Lerp(startPos, endPos, i);
    }
}

Eric mentioned the FPS can vary and it will throw unexpected high values for deltaTime. I think this deserves more attention.

1.

In practice, when I was using Time.deltaTime and Quaternion.Lerp (it’s actually irrelevant if it’s quaternion or vector3 or whatever lerp) I was getting this nasty looking error:

CompareApproximately (aScalar, 0.0F)
UnityEngine.Quaternion:Lerp(Quaternion, Quaternion, Single)

This is because, eventually the deltaTime would make it bigger than 1, thus clamping to 1 and, in my code, for the 1st frame only, the destination rotation was a very bad (0, 0, 0, 0);. The point here is: both the clamping and the bad value were unexpected, resulting into a little hard to debug error. Mostly because it’s hard to reproduce.

The true effect was nothing that makes the lerping noticeable. But this is something to consider for point 2:

2.

As we can see in (1) there is this extra problem with the trick as it will not behave the same every time, precisely because the FPS can vary. But it’s not that easy to reproduce the trick also, because it kind of balances out processor speeds and so it’s one of the best tricks to make a Lerp work similarly in different machines.

For instance, a much simpler way to achieve the same ease out trick effect is to just set a const:

transform.rotation = Quaternion.Lerp(originalRotation, targetRotation, 0.1f);

But if you run that on a i7 quad core versus on an iPhone, you’ll already see the difference.

TL;DR

So, there is in fact a good use for Time.detalTime on lerping. You just need to be aware of the caveats!

I have a potentially simpler solution to that of @Eric5h5’s involving the “slow down while approaching target” effect (the curvature is slightly different though):

curPos = Vector3.Lerp(curPos, endPos, 1-Pow(2, -Time.deltaTime * rate));
curPos = current position
endPos = end position
  • 2 - arbitrary, could be anything larger than 1 (changing it does the same thing as changing the rate).

  • rate - the rate of the movement - higher value means approaching the end position faster.


Here’s an example to prove this works:

For simplicity let’s just assume the positions are real numbers.

curPos = 1 (at the beginning), endPos = 0, rate = 1. 

We’ll track curPos over the duration of 1 time unit to determine the final result in two cases:

  1. If Time.deltaTime = 1:

in this case the calculation will run 1 time:

curPos = Lerp(1, 0, 1-Pow(2, -1*1)) = Lerp(1, 0, 0.5) = 0.5;

so the final result is 0.5.

  1. If Time.deltaTime = 0.25:

in this case the calculation will run 4 times:

curPos = Lerp(1, 0, 1-Pow(2, -0.25*1)) = Lerp(1, 0, 0.16) = 0.84;
curPos = Lerp(0.84, 0, 1-Pow(2, -0.25*1)) = Lerp(0.84, 0, 0.16) = 0.705;
curPos = Lerp(0.705, 0, 1-Pow(2, -0.25*1)) = Lerp(0.705, 0, 0.16) = 0.59;
curPos = Lerp(0.59, 0, 1-Pow(2, -0.25*1)) = Lerp(0.59, 0, 0.16) = 0.5;

so the final result is still 0.5, regardless of Time.deltaTime’s value.

The reason this works is actually pretty simple.

Going with the previous example, what we actually did was this:

  1. If Time.deltaTime = 1:
curPos = 1*2^(-1)
  1. If Time.deltaTime = 0.25:
curPos = 1*2^(-0.25) *2^(-0.25) *2^(-0.25) *2^(-0.25) = 1*2^(0.25-0.25-0.25-0.25) = 1*2^(-1)

I hope this can help someone.

Hopefully someone reads this part as well because this took me some testing to figure out since Im such a noob…

If you move an gameObject over frames like this:

currentDistance = (currentFrame / target_Frame);
gameObject_ToMove.transform.position = Vector3.Lerp (startingPosition, targetPosition, currentDistance);

To make it frame independent it’s incredibly simple. You just want to add Time.deltaTime onto the end of the current distance like so:

currentDistance = (currentFrame / target_Frame) + (0.01f * Time.deltaTime);
gameObject_ToMove.transform.position = Vector3.Lerp (startingPosition, targetPosition, currentDistance);

The reason I use 0.01f is because if you just add Time.deltaTime onto the end of your equation alone it will go too fast.