Using Vector3.Lerp() correctly in Unity

Filed under Uncategorized

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() returns start
  • At percentage = 1.0, Lerp() returns finish
  • At 0.0 < percentage < 1.0, Lerp() returns a point between start and finish.
  •    So at percentage = 0.5, it returns the point exactly halfway between start and finish.
  •    And at percentage = 0.10, it returns a point very near to start

etc.

Linear Interpolation Example

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.

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.

  1. 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
  2. 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.
  3. 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:

(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)


Additional Reading:

33 Comments

  1. G says:

    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.

    • BlueRaja says:

      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.

      • TheMayorOfCanTown says:

        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.

        • BlueRaja says:

          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.

          • TheMayorOfCanTown says:

            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!

  2. Ben says:

    I found this very useful. Question though, did I miss it or you didnt talk about how to implement adjustable speed?

    • BlueRaja says:

      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

  3. arumiat says:

    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..

    • BlueRaja says:

      Of course there’s a way! When we vary the third parameter, percentage, linearly each frame, we get a linear result. If we vary percentage 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 in Math.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.

  4. Andy says:

    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.

  5. Dan says:

    hey, very helpful post indeed. thanks a bunch!

    also, could you please upload your monodevelop color scheme?

    thx!

  6. Yoey says:

    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.

  7. Desprez says:

    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.

  8. Azar says:

    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.

    • BlueRaja says:

      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.

      • Azar says:

        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

        • Blargh says:

          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.

  9. neon_ace says:

    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.

  10. TG_KG says:

    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)

  11. DavidSearson says:

    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.

  12. Phoenix_SnowFire says:

    This is the best explanation for LERP, make more tutorials , ur explanation is soo…good . Please make tutorials on Quaternion and Euler angles

  13. Matthew says:

    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.

  14. Spencer says:

    Does the same apply to Quaternion.Lerp()?

  15. tim says:

    its quite helpful. appreciate it.

  16. Blake says:

    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.

  17. 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 😀

  18. Daniel Awesome says:

    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!

  19. C says:

    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.

  20. Bipin gosain says:

    Thanks man! Nice explaination.

  21. Peter Keller says:

    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.

  22. Alexander Nava says:

    Thank you this is an amazing article!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*