A lot of Unity 3D tutorials online use Unity’s linear interpolation incorrectly, including the official video tutorials(!). However, it’s actually very easy to use, once you understand how it works.
The prototype for Vector3.Lerp
looks like this
static Vector3 Lerp(Vector3 start, Vector3 finish, float percentage)
What it does is simple: it returns a point between start and finish, based on the value of percentage
:
- At
percentage = 0.0
,Lerp()
returnsstart
- At
percentage = 1.0
,Lerp()
returnsfinish
- At
0.0 < percentage < 1.0
,Lerp()
returns a point betweenstart
andfinish
. - So at
percentage = 0.5
, it returns the point exactly halfway betweenstart
andfinish
. - And at
percentage = 0.10
, it returns a point very near tostart
etc.
This explains why it’s called linear interpolation: It moves smoothly (interpolates) at a constant speed (linearly) between two points!
How to use Lerp correctly
To use Lerp()
correctly, you simply have to make sure that you pass the same start
and finish
values every frame while moving percentage up from 0.0 to 1.0 each frame.
Here’s an example. When the spacebar is pressed, we’ll lerp our object 10 spaces forward over a period of 1 second.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using UnityEngine; using System.Collections; public class LerpOnSpacebarScript : MonoBehaviour { /// <summary> /// The time taken to move from the start to finish positions /// </summary> public float timeTakenDuringLerp = 1f; /// <summary> /// How far the object should move when 'space' is pressed /// </summary> public float distanceToMove = 10; //Whether we are currently interpolating or not private bool _isLerping; //The start and finish positions for the interpolation private Vector3 _startPosition; private Vector3 _endPosition; //The Time.time value when we started the interpolation private float _timeStartedLerping; /// <summary> /// Called to begin the linear interpolation /// </summary> void StartLerping() { _isLerping = true; _timeStartedLerping = Time.time; //We set the start position to the current position, and the finish to 10 spaces in the 'forward' direction _startPosition = transform.position; _endPosition = transform.position + Vector3.forward*distanceToMove; } void Update() { //When the user hits the spacebar, we start lerping if(Input.GetKey(KeyCode.Space)) { StartLerping(); } } //We do the actual interpolation in FixedUpdate(), since we're dealing with a rigidbody void FixedUpdate() { if(_isLerping) { //We want percentage = 0.0 when Time.time = _timeStartedLerping //and percentage = 1.0 when Time.time = _timeStartedLerping + timeTakenDuringLerp //In other words, we want to know what percentage of "timeTakenDuringLerp" the value //"Time.time - _timeStartedLerping" is. float timeSinceStarted = Time.time - _timeStartedLerping; float percentageComplete = timeSinceStarted / timeTakenDuringLerp; //Perform the actual lerping. Notice that the first two parameters will always be the same //throughout a single lerp-processs (ie. they won't change until we hit the space-bar again //to start another lerp) transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete); //When we've completed the lerp, we set _isLerping to false if(percentageComplete >= 1.0f) { _isLerping = false; } } } } |
How to use Lerp incorrectly
In our above example, we call Vector3.Lerp()
like this:
transform.position = Vector3.Lerp(_startPosition, _endPosition, percentageComplete);
However, the method for calling Lerp you see in many tutorials online looks like this:
transform.position = Vector3.Lerp(transform.position, _endPosition, speed*Time.deltaTime);
Before I explain what’s wrong with this, please take a moment and try to figure out for yourself what this code will do, and why it’s wrong
…
…
…Ready?
The issue with the above code is that the first parameter to Lerp()
, transform.position
, changes every frame! Additionally, the percentage
parameter (the third parameter) does not increase from 0.0 to 1.0, but instead is set to the completely arbitrary value speed*Time.deltaTime
.
What this code actually does is move the object speed*Time.deltaTime
-percent closer to _endPosition
every frame.
There are several problems with this.
- It’s non-linear ie. the speed is not constant. This is easy to see in the official video tutorial; the object moves quickly at first, and slows down as it nears its destination
- The object’s speed varies with the user’s framerate. The higher the user’s framerate, the faster the object will move. This defeats the purpose of using
FixedUpdate()
to begin with, which is supposed to alleviate this problem. - The object never reaches its destination since we only move a percentage closer each frame. Actually, due to floating-point rounding, it may or may not ever reach its destination. Additionally, because of problem 2, whether or not it ever reaches its destination depends on the user’s framerate. Ouch!
The implementation of Lerp
As a final note, I’d like to share how Lerp()
is typically implemented. This section is not required, but mathematically-inclined readers will find it interesting.
Implementing Lerp()
is actually remarkably simple:
1 2 3 4 5 6 7 8 9 10 11 | public static Vector3 Lerp(Vector3 start, Vector3 finish, float percentage) { //Make sure percentage is in the range [0.0, 1.0] percentage = Mathf.Clamp01(percentage); //(finish-start) is the Vector3 drawn between 'start' and 'finish' Vector3 startToFinish = finish - start; //Multiply it by percentage and set its origin to 'start' return start + startToFinish * percentage; } |
(Edit Oct 2016: A commenter correctly pointed out that this implementation of Lerp()
has floating-point rounding issues. See his comment for a more accurate version)
48 Comments
They’re not using it incorrectly. They’re using it to approximate exponential decay to the target position.
current = Lerp(current, target, T)
At each frame, the position is moved T percent toward target. Using Time.deltaTime in T makes the movement framerate independent.
Using it this way isn’t always appropriate, but it works well for smoothing out camera movements where responsiveness is still important. Also works well with Quaternion.Slerp for rotations in similar circumstances.
If you want exponential interpolation, you should be using an exponential interpolation function, not using a linear interpolation one incorrectly.
Not only does that convey the intent of the code better, but it also won’t have the frame-rate-dependency and floating-point rounding issues that I mentioned above. The way the video uses it is simply wrong.
Framerate-dependency is not an issue here because the code from the video is not using FixedUpdate() like your code. Using Lerp with deltaTime within Update(), like in the video, will produce framerate-independent results.
Nope, that is not true, the code in the video is still framerate-dependent. Moving 4% towards a destination 25 times a second is not the same as moving 2% towards it 50 times a second.
Oh man you are actually right, I never thought of it that way. Well I have been using Lerp like a fool for years now. Thanks for explaining this!
BlueRaja:
Can you explain the 4% at 25 times a second vs the 2% at 50 time a second again. I am just not getting what you mean.
Hi guys,
I must be missing something really basic here…
Why is 4% of “x” times 25 not the same as 2% of “x” times 50? Isn’t the distance covered the same?
Because ‘x’ is not the same every frame, so the effect accumulates. The actual statement is that 1.02^(25) ≠ 1.04^(50)
How exactly might you suggest handling a case where start and end are, in fact, always changing (camera code, as mentioned by TheMayorOfCanTown, being one such scenario). You describe, in essense, a simple linear tween system with two keyframes and a single normalized t value, but cases like camera code with continuously moving targets cannot be easily divided into single tween instances (I should imagine that “restarting” the tween every time the target position moves would also present issues).
I can see why framerate dependency is an issue still though. I wonder if anyone has tried to derive an equation that would preserve the non-continuous aspect of the “wrong” usage of lerp while still maintaining framerate independence and giving the desired exponential decay?
I would measure the distance in between the two points, and figure how much of that distance should be traveled during this frame. If the start/end point are always changing then doing it this way will just insure you are moving the same amount per frame. Thus the third argument will not be accumulative, but a constantly changing fraction of the distance that is equal to the travel speed, and frame time.
I found this very useful. Question though, did I miss it or you didnt talk about how to implement adjustable speed?
Speed is just distance / time. Using the variable names in my example-script, it’s
speed = distanceToMove / timeTakenDuringLerp
.So to increase the speed, either increase the distance or decrease the time.
If you have a specific speed you want to go at, set
timeTakenDuringLerp = distanceToMove / speed
Hi BlueRaja
Thanks for this, as a complete beginner coder was of great help. If one wanted to lerp smoothly so that initially it’s fast, then medium speed then slows down before destination, are there some easy modifications to this code to allow for this? Other tutorials seem to suggest there is..
Of course there’s a way! When we vary the third parameter,
percentage
, linearly each frame, we get a linear result. If we varypercentage
in a non-linear way, we get a non-linear result.So, we need to do some fancy math to
percentage
to change it in such a way that it increases quickly near the start and slowly near the end, but still starts at 0 and ends at 1.The simplest way I know of doing this is raising
percentage
to a power. For instance, if we pass inMath.pow(percentage, 0.5)
, the distance-over-time graph will look like this.So, the final call looks like this:
Vector3.Lerp(start, finish, Math.pow(percentage, 0.5));
I would probably pull that into a separate function called
ExponentialInterp()
or something. You can also play with the power,0.5
, to make it fit your needs.Thanks so much for this! I was just looking over some Lerp that I’ve been using for a long time. It had the similar Time.deltaTime * speed. I had never questioned it until tonight. How obviously silly a thing.
hey, very helpful post indeed. thanks a bunch!
also, could you please upload your monodevelop color scheme?
thx!
There are several things that I like about this script in the context of teaching somebody how and why a script is doing what it does.
For one, It works, which is always a big plus.
I appreciate the comments which are thorough but to the point. A lot of times I just get confused by people’s comments. Starting booleans with “_is” is a nice touch. I don’t know if that’s a common thing but it helped me follow the program flow so I might just have to adopt that one.
But what I absolutely love about this is that all the lerp parameters are broken down and captured by your variables, so you can track the lerp from start to finish through the code. Very useful and much improved from any other explanation I’ve come across.
Thanks for sharing this. It’s a big help. Now I’ll probably crash and burn when applying this approach to rotation but hey you never know.
Thank you, thank you, thank you!
I always wondered why my lerps were never working correctly. The examples in many tutorials really threw me off.
Thanks for sharing this, I have a question, is there any way to use update instead of FixedUpdate and make it frame-rate-dependency, am using this to move npc’s along a path after all i dont want them to slow down on slower devices.
I think you’re confused? If the code were framerate-dependent, the npc’s would slow down on slower devices. However, since it’s framerate-independent already, they will have the same speed regardless of device speed.
yes that’s correct, but i noticed a drop on frame rate when i used the code in FixedUpdate to move a huge amount of npc’s, they all have rigidbuddy attached to them just for collision reporting, so i moved the code to the Update method and the problem disappeared, that’s why i want to be frame-rate-d, any advice ? thanks
You’re super duper off, man. What’s likely happening is that your Update() method is firing more than 60x /sec, so it looks like things are running smoothly. You want to either multiply every time affected variable by Time.deltaTime, or move things to FixedUpdate. If you have slowdown issues it’s not Update/FixedUpdate that’s the issue. It’s you’re code. You want everything, always, to be framerate dependent. Like really… always.
holy crap! can you please do more tutorials? your explaining is simple for slow learners like me.
short and to the point with pictures. plus you use names that makes sense for variables.
This is a great explanation of LERP and has just saved me a whole bunch of time trying to make my script work properly. I have noticed one item worth mentioning, if the Update event should re-occur during the FixedUpdate, then StartLerping() runs again and the _timeStartedLerping is reset to a new time, thus the Lerp overshoots the finish position.
Fix for this is to change the following within Update()
if(Input.GetKey(KeyCode.Space) && !_isLerping)
Works great how you explained.
For the 2 hours I searched online and through docs,(Unity docs and other0 I needed a slow movement of a transform from one position to another. This works good. Thanks.
This is the best explanation for LERP, make more tutorials , ur explanation is soo…good . Please make tutorials on Quaternion and Euler angles
Just want to thank you for writing this.
After a ton of googling this is the page that finally made me understand how to use Lerp.
Does the same apply to Quaternion.Lerp()?
its quite helpful. appreciate it.
Useful tutorial! I just have one question. I have enemies that are spawning at a certain Transform objects position. The end points and start points that I had for my Lerp were Transform objects using empty game objects. How am I supposed to set my start and end points using Vector 3? I tried manually setting them and no lerp was working whatsoever.
I had a problem with a rough camera movement cause i was using Lerp the wrong way, Great explanation in why to use the correct way and thanks for this post! Definitely helpful! 🙂 Will surely recommend this to fellow devs 😀
You have magically saved my brain from melting! Thank you so much for this. I’ve been trying to understand why my lerps never work the way they are supposed to and Finally I get it.
Updating the start position with transform.position made sense until I understood that it’s not T… but P for percentage of time.
Holy crap its so simple it is almost embarrassing that I never saw it.
THANK YOU!
Thanks a lot! Helped get the right experience in the game, which wasn’t possible with the more common way lerp is used in tutorials.
Thanks man! Nice explaination.
The demonstration Lerp() function you have is likely not numerically stable because it amplifies floating point representation error. Try val = (1-t)*v0 + t* v1; for a better interpolation from v0 to v1 when t moves from 0.0 to 1.0.
Good point! I won’t change the code (as it’s really meant to be educational anyways), but I’ll mention this comment in the post. Thanks!
Hi I was just wondering if you could explain why this needs to be done a little bit more mundanely, and maybe implemented in the code in the article also?
Here’s my understanding: say for example start is very large compared to the distance {finish-start}. {t*(finish – start) + start} might just round to start, especially when t is also small.
So if instead, you use {(1-t)*start + t*finish}, the actual value is the same, but the intermediate variables {(1-t)*start} and {t*finish} are of the same general magnitude and therefore less likely to suffer from rounding issues.
It’s because
start + startToFinish
might not exactly equalfinish
, due to floating-point rounding. So he’s saying the final line should readreturn (1-percentage)*start + percentage*finish;
I didn’t include this in the article because I thought it would be more difficult for most people to understand.
Please write out the Lerp() function. what is: v0, V1. Please use Raja’s code.
Thank you this is an amazing article!
This is wonderful, such smooth lerping! =)
Thanks ³
What about moveTowards() ??????
Is it the same????
What about moveTowards???
Is it the same?????
Thanks a million!!! You save me my life!!!!
I love your tutorial! very helpful and descriptive!
My projectiles spawn in a radius at random angles, then they gradually rotate towards the initial target over a customizable time. I’ve been struggling for 2 days to get the timing/rotation correct. This has worked for me, finally! Really appreciate your tutorial. Thank you!
Trackbacks/Pingbacks