### Math Magician – Lerp, Slerp, and Nlerp

Man, I wish I had this blog going through school, because this place has become my online notebook. Game development is like riding a bike – but math, for me, can have a hard time stickin’. I’ve been working in databases so long that I have a lot of bit-shifting down, but my matrix and vector math is starting to lack. So, a creative way for me to remember all these algorithms is to try to explain them. Today, I’m going through Linear Interpolation, Spherical Linear Interpolation, and Normalized Linear Interpolation.

The Code

I’m starting off with the code to show the similarities and differences with each. One thing similar right off the bat is the parameter list: each function takes the same 3 – A `start`, an `end`, and a `percent`. The `start` and `end` can be Vectors, Matrices, or Quaternions. For now, lets use Vectors. The `percent` is a scalar value between 0 and 1. If `percent == 0`, then the functions will return start, if `percent == 1`, the functions return end, and if `percent == 0.5`, it returns a nice average midpoint between the 2 vectors.

Lerp: The basic Linear Interpolation function. Lets break this down, right to left. First, we have `end - start`, which will pretty much return a vector the same distance as `start` and `end`, but represented as if it’s `start` vector was `{0, 0, 0}`. We multiply that vector by `percent`, which will give us a vector only `percent` as long (so if percent is 0.5, the vector now is only half as long as it was). We then add `start` back to it to return it to its normal position. Done.

`Vector3 Lerp(Vector3 start, Vector3 end, float percent){     return (start + percent*(end - start));}`

Lerp is useful for: Transitions from place to place over time, implementing a Health Bar, etc. Pretty much going from point A to point B.

Slerp: Slerp is a very powerful function. It’s become very popular in the game industry for it’s ease of use and significant result, however, people tend to ignore its drawbacks. Slerp travels the torque-minimal path, which means it travels along the straightest path the rounded surface of a sphere, which is a major plus and part of the reason it’s so powerful, as well as the fact that it maintains constant velocity. But Slerp is non-commutative, meaning the order of how the vectors/matrices/quaternions are passed in will affect the result. A call to `Slerp(A, B, delta)` will yield a different result as compared to `Slerp(B, A, delta)`. Also, Slerp is computationally expensive because of the use of mathematical functions like `Sin()`, `Cos()` and `Acos()`.

`// Special Thanks to Johnathan, Shaun and Geof!Vector3 Slerp(Vector3 start, Vector3 end, float percent){     // Dot product - the cosine of the angle between 2 vectors.     float dot = Vector3.Dot(start, end);     // Clamp it to be in the range of Acos()     // This may be unnecessary, but floating point     // precision can be a fickle mistress.     Mathf.Clamp(dot, -1.0f, 1.0f);     // Acos(dot) returns the angle between start and end,     // And multiplying that by percent returns the angle between     // start and the final result.     float theta = Mathf.Acos(dot)*percent;     Vector3 RelativeVec = end - start*dot;     RelativeVec.Normalize();     // Orthonormal basis     // The final result.     return ((start*Mathf.Cos(theta)) + (RelativeVec*Mathf.Sin(theta)));}`

Slerp is useful for: Rotation, mostly.

Nlerp: Nlerp is our solution to Slerp’s computational cost. Nlerp also handles rotation and is much less computationally expensive, however it, too has it’s drawbacks. Both travel a torque-minimal path, but Nlerp is commutative where Slerp is not, and Nlerp also does not maintain a constant velocity, which, in some cases, may be a desired effect. Implementing Nlerp in place of some Slerp calls may produce the same effect and even save on some FPS. However, with every optimization, using this improperly may cause undesired effects. Nlerp should be used more, but it doesn’t mean cut out Slerp all together. Nlerp is very easy, too. Just normalize the result from `Lerp()`!

`Vector3 Nlerp(Vector3 start, Vector3 end, float percent){     return Lerp(start,end,percent).normalized();}`

Nlerp is useful for: Animation (in regards to rotation and such), optimized rotation.

The trouble with Lerp, Slerp and Nlerp to most people is that they don’t truly understand the functions. Most people will post “Hey, I have a problem with this” and someone will say use one form of Linear Interpolation, and just patch it in. It’ll work, but will they understand why it works? Could it be improved? I would recommend taking some time and reading articles on the different Linear Interpolations – there are more out there I didn’t mention, and all with different purposes.

That’s all for now. I’ll find more math to put up in the weeks to come. Till then, take care!

This post is dedicated to the staff at ZeeGee Games. I may have lost the battle, but I will not lose the war! I will be a Game Dev Jedi!

Also, if you’re part of the IGDA, Vote For Grant! (This link is really old by now, though…)

## 32 thoughts on “Math Magician – Lerp, Slerp, and Nlerp”

1. Thanks for the slerp code!

I noticed you have a clamp after the dot product. That should not be needed because dot should always return a number between -1 and 1

One thing I learned when implementing this was that you don’t need acos() when you know for a fact that your two input vectors are right angles. For example, in the case where your vector was derived from a cross product. This is because the dot product of right angles == 0, and acos(0) == 1.5708… or PI * 0.5, which when compiled as a constant would be much faster. Also in the case of right angles, you could do without the relative vector since it would be the end vector.

Thanks again!

1. Excellent catch! Thanks for the tip – I’ll work that into the code I posted. Thanks a lot!

2. I will disagree on he clamp not being needed. I work in an environment with a lot of math and vector nodes available. And I have had to do my own implementation of the Angle node, precisely to add a clamp because of floating point errors. Don’t ask me why… yes, the vectors are normalized, but it would just once in a full moon through a FP error, go over 1 or -1 and make arccos trip a -1.#IND.

From my experience I say keep it, but I don’t know if here the same issue will take place.

Cheers

3. Shauns info is misleading. In case of using already normalized vectors (start, end) no clamp is required. When using world position vectors without clamp dot() returns NAN.

Anyway, this implementation is wrong and does not work.
For example using Vector3.Slerp in Unity3d it behave completely different.

1. Now, when you say “this implementation is wrong,” are you referring to Shaun’s or to mine? I just want to saty on top of my corrections, since this is pretty much the most popular post on my site (thanks to viewers like you).

2. Geof says:

Great post, very informative, don’t forget to bin off that clamp!!

1. Consider it done! Sorry for being so late to do so!

1. Whoa, don’t get so antsy! The above posters are wrong. It is quite frequent for vector dot product routines to return values outside of [-1, 1] due to floating point error (or just that one of the input vectors got to be a little bit non-unit some other way) and then acos will throw an exception. You want to always *always* clamp before an inverse trig routine…

2. I mean, let me put it this way: due to limited numerical precision, it is actually impossible for your Vector3 to be exactly 1 unit long except in special cases. So… you do think it’s going to be less than 1 unit long or greater than 1 unit long? Or either, quasi-randomly?

3. You do bring up a valid point, and I have learned a few lessons from floating point precision. I’ll put it back in for, if nothing else, a paranoid check.

Thank you all for your input on this!

4. Also, just as an aside, I just realized who you are. I’m not going to completely nerd-out like I am doing in real life right now, but thank you for coming here and commenting. I feel like my blog has now some credibility, and it’s thanks to you and all the other successful Indie devs out there that make me feel like I have a chance in the daunting world of Video Games.

Thank you, sir.

2. I will disagree on he clamp not being needed. I work in an environment with a lot of math and vector nodes available. And I have had to do my own implementation of the Angle node, precisely to add a clamp because of floating point errors. Don’t ask me why… yes, the vectors are normalized, but it would just once in a full moon through a FP error, go over 1 or -1 and make arccos trip a -1.#IND.

From my experience I say keep it, but I don’t know if here the same issue will take place.

Cheers

3. Alban says:

I do not really understand the argument of non-commutativity of Slerp. If I consider the Lerp interpolation, it leads to similar behaviour: Lerp(Q1,Q2,percent) is also different from Lerp(Q2,Q1,percent).

4. Anthony Smith says:

I know this post is a few years old, but, for the Slerp function, the dot product of a vector does not always return a value between -1 and 1. For instance, the dot product of these two vectors and is -353. To get that value between -1 and 1 (the value to use with the arccosine), you would need to divide it by the magnitudes of both vectors.

1. Anthony Smith says:

It seems it didn’t like the brackets around the two vectors: (1, 50, -25) and (12, -7.3, 0)…

1. Shaun says:

If the input vectors are normalized (your vectors are not), the dot product will be -1 to 1. If you need a vector of the same length as the input, you can grab the lengths before you normalize and then multiply that to the slerped vector, probably interpolating the lengths as well if both inputs are different lengths.

2. Anthony Smith says:

Except nowhere in the function does it ensure that the vectors are normalized, so blindly inputting a vector (like the ones that I posted) would produce a bad result.

3. Shaun says:

I personally like that the code is more modular this way. I am generally already working with normalized vectors, so having an extra normalization step in there would be a waste of cycles in many of my cases. If it was always wanted, then you might make a function for it, but it doesn’t make sense to do that if it’s not always what is wanted. Perhaps there could be a comment that the input vectors should be normalized.

Or, if cycles are not and will never be a concern for your application, you could always make a “safe” version that ensures normalization.

5. great post ! thanks for slerp code, now i can understand it !

6. aa says:

Your sites mobile compability is low. i cant see the right part of the site. so some text was invisible

1. I try to compare the desktop and mobile versions whenever I can. Can you upload a screenshot for me?

7. Eric Wieser says:

I don’t know what you mean by this:

> But Slerp is non-commutative, meaning the order of how the vectors/matrices/quaternions are passed in will affect the result. A call to Slerp(A, B, delta) will yield a different result as compared to Slerp(B, A, delta).

Of course `Slerp(A, B, delta)` is different to `Slerp(B, A, delta)`. That statement is true of all interpolation functions. Were you trying to say `Slerp(A, B, delta)` is different to `Slerp(B, A, 1-delta)` (which would be more interesting, but false)? I think the case you’re thinking of is `Slerp(A, Slerp(B, C, dbc), dab) != Slerp(Slerp(A, B, dab), C, dbc))`,

8. I know this is an ancient post, but nobody seemed to comment that the line “Mathf.Clamp(dot, -1.0f, 1.0f);” does nothing. The results of Clamp() needs to be assigned to a variable (probably to dot, in this case).

1. Shaun says:

That is correct. I was not familiar with the exact library being used (Mathf), and presumed it was doing it in place on the input variable. But it looks like Mathf is a Unity thing and does not modify the variable in place, but returns a value. That value in this case is being dropped.

This site uses Akismet to reduce spam. Learn how your comment data is processed.