Tuesday, December 11, 2018

unity - Why use Time.deltaTime in Lerping functions?


To my understanding, a Lerp function interpolates between two values (a and b) using a third value (t) between 0 and 1. At t = 0, the value a is returned, at t = 1, the value b is returned. At 0.5 the value halfway between a and b is returned.


(The following picture is a smoothstep, usually a cubic interpolation)


enter image description here


I have been browsing the forums and on this answer I found the following line of code: transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);


I thought to myself, "what a fool, he has no idea" but since it had 40+ upvotes I gave it a try and sure enough, it worked!


float t = Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, t);

Debug.Log(t);

I got random values between 0.01 and 0.02 for t. Shouldn't the function interpolate accordingly? Why do these values stack? What is it about lerp that I do not understand?



Answer



See also this answer.


There are two common ways to use Lerp:


1. Linear blending between a start and an end


progress = Mathf.Clamp01(progress + speedPerTick);
current = Mathf.Lerp(start, end, progress);


This is the version you're probably most familiar with.


2. Exponential ease toward a target


current = Mathf.Lerp(current, target, sharpnessPerTick);

Note that in this version the current value appears as both the output and an input. It displaces the start variable, so we're always starting from wherever we moved to on the last update. This is what gives this version of Lerp a memory from one frame to the next. From this moving starting point, we then then move a fraction of the distance toward the target dictated by a sharpness parameter.


This parameter isn't quite a "speed" anymore, because we approach the target in a Zeno-like fashion. If sharpnessPerTick were 0.5, then on the first update we'd move halfway to our goal. Then on the next update we'd move half the remaining distance (so a quarter of our initial distance). Then on the next we'd move half again...


This gives an "exponential ease-out" where the movement is fast when far from the target and gradually slows down as it approaches asymptotically (though with infinite-precision numbers it will never reach it in any finite number of updates - for our purposes it gets close enough). It's great for chasing a moving target value, or smoothing a noisy input using an "exponential moving average," usually using a very small sharpnessPerTick parameter like 0.1 or smaller.




But you're right, there is an error in the upvoted answer you link. It's not correcting for deltaTime the right way. This is a very common mistake when using this style of Lerp.


The first style of Lerp is linear, so we can linearly adjust the speed by multiplying by deltaTime:



progress = Mathf.Clamp01(progress + speedPerSecond * Time.deltaTime);
// or progress = Mathf.Clamp01(progress + Time.deltaTime / durationSeconds);
current = Mathf.Lerp(start, end, progress);

But our exponential easing is non-linear, so just multiplying our sharpness parameter by deltaTime will not give the correct time correction. This will show up as a judder in the movement if our framerate fluctuates, or a change in the easing sharpness if you go from 30 to 60 consistently.


Instead we need to apply an exponential correction for our exponential ease:


blend = 1f - Mathf.Pow(1f - sharpness, Time.deltaTime * referenceFramerate);
current = Mathf.Lerp(current, target, blend);

Here referenceFramerate is just a constant like 30 to keep the units for sharpness the same as we were using before correcting for time.





There's one other arguable error in that code, which is using Slerp - spherical linear interpolation is useful when we want an exactly consistent rate of rotation through the whole movement. But if we're going to be using a non-linear exponential ease anyway, Lerp will give an almost undistinguishable result and it's cheaper. ;) Quaternions lerp much better than matrices do, so this is usually a safe substitution.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...