Sergio Flores

June 22 2017 | 12:00 AM

The Art of Lerp

What exactly is a LERP?

Using Lerp with Time.deltaTime?

Real world mistakes

current = lerp(initial,target,Time.deltaTime) will reach target in 1 second.

That's not luck. You use Time.deltaTime/ Duration, and it works.

"current = Mathf.Lerp(current, final, Time.deltaTime*adaptionSpeed);

That works perfectly fine for any consistent units, and gives a basic Easing."

private float current = 0.0f;

private float target = 1.0f;

private float adaptionSpeed = 1.0f;

private int frames = 0;



void Update() {

frames++;

current = Mathf.Lerp(current, target, Time.deltaTime*adaptionSpeed);

Debug.Log(frames + " -> " + current);

}





What is the use for Lerp?

private bool anim_active;

private float anim_time;

private float anim_duration;

private float anim_start_value;

private float anim_end_value;



void StartAnimation(float startValue, float endValue, float duration)

{

anim_start_value = startValue;

anim_end_value = endValue;

anim_time = Time.time;

anim_duration = duration;

anim_active = true;

}



void Update() {

if (anim_active) {

float delta = (Time.time - anim_time); // calculate the time elapsed since the start of the animation, in seconds

delta /= duration; // divide the time by the duration to make it into the range "0 to 1"

if (delta>1) { // check if we reached the end of the animation

delta = 1;

anim_active = false;

}

target_value = Mathf.Lerp(anim_start_value, anim_end_value, delta);

}

}

Lerp'ing 3 values instead of 2?

float Lerp(float a, float b, float c, float x)

{

if (x<0.5f) {

x *= 2.0f; // convert from "0 to 0.5" to "0 to 1"

return Mathf.Lerp(a, b, x);

}

else {

x -= 0.5f; // convert from "0.5 to 1" to "0 to 0.5"

x *= 2.0f; // convert from "0 to 0.5" to "0 to 1"

return Mathf.Lerp(b, c, x);

}

}

Lerp'ing Vectors?

Inverting a Lerp?

Inverting a 2D Lerp?

What about Bilinear Lerp?



Bilinear texture sampling, useful if you for some reason need to take an interpolated color value from a texture (which means, getting a pixel from a texture using float coordinates instead of integer values!). By the way, Unity does have Texture2D.BilinearLerp already.



Sampling from an heightmap. If you have a terrain defined by an heightmap, and you want to know the height at a specific point, you can take the 4 nearest heightmap values and do a BilinearLerp on them.



Generating procedural geometry. Imagine you want to are creating a procedural mesh and want to generate a circular hole in the middle of an inclined plane. That seems very complex right?

Or you can just take the 4 corners of the surface, and do a BilinearLerp!



Barycentric interpolation



Creating your own Lightmapper



Extracting UVs from collision detection



Sampling heights from terrain meshes



Do a Trilinear Lerp now!



Color transforms. Converting from one color space to another, can be done using trilinear lerping, since color spaces can be represented as a cube (eg: Imagine each axis of the cube as a color component, RGB, or HSL for example). Depending on the color spaces envolved, usually there are easier ways to do this though.



Continuing with the color operations, you can also use TrilinearLerping to apply a Color LUT to an image. This is how Color Grading shaders work!



If you are working with voxels or other data structures that deal with points in 3d dimensions, it can be useful. For example, some years ago I did a tech demo that used a big 3d grid to represent shadow information at each (x,y,z) point in an game level.



What about Quaternions?

Tell me about Easing!

Conclusion

If you are making a game, you probably already learned about Lerp functions, aka Mathf.Lerp() in Unity, lerp() in GameMaker and similar names in other engines.In any case, this article will be interesting for both newbies and more experienced developers.I've seen many developers use Lerp without understanding what it is, including many that just think it is just a function to ease a value over time, yuck!Later in this article we'll learn how to properly do easing with Lerps, but for now, let's speak about the basics.Lerp means linear interpolation, and is used to "mix" two values (we'll call them A and B) based on a third value, a percentage, which we will call X for now (depending on the engine and language, you might see it be called T, S or even other letters!).The third value (X) is a float value from 0 to 1, which you can think as a percentage that goes from 0 to 100%.Other way to visualize it, is to imagine a line with value A in the left, and value B in the right.The interpolation value will be used to pick a number in between. That's where the "linear" comes from.The actual implementation of the Lerp function is actually quite simple.lerp(a, b, x) = a * (1.0f - x) + b * x;or more simplylerp(a, b, x) = a + (b -a ) * x;From this formula you can get that the third parameter is actually just the percentage of the B.You then multiply it with B to calculate the percent value, and sum it with the percentage of A (which is equal to 100% minus the percent of B).Many Unity devs like to pass Time.deltaTime as the third argument for Lerp(). Well, that seems to works due to an interesting mathematical coincidence of Unity using seconds as time unit, thus each Time.deltaTime (aka frame duration) falls nicely inside the "0 to 1" range.However if they did use milliseconds or microseconds for time unit as some other engines do (eg: Game Maker ), it would not work at all!This is an controversial fact, and sadly there is a whole cargo culto approach partly endorsed by the official Unity documentation which shows code snippets doing this in some examples.In fact, if you reached this part of the article and started thinking "what? you are wrong", I suggest you to first read at least this section and the following section before taking any conclusions.Let's examine exactly why this is a problem. When you do this, note that Time.deltaTime will usually be a very small value. Imagine your game is running at 60FPS, then Time.deltaTime will be 1/60 which is 0.01666.You remember the lerp() formula we just saw in the previous section right?The third argument means the percentage of B, so, in other words, by doing this you'll be mixing 99% of A and 1% of B by using Time.deltaTime, meaning the value will never reach B, but after a bit it will be close enough.To put it simply, the third parameter expects a number representing a percentage not a time value.You can convert from time values to percentages, but should not mix them!Here's a classical mistake, found in a reader's comment:As we know, according to the documentation, Time.deltaTime is a property that returns exactly the time in seconds spent in the last frame.This can be estimated based on the frame rate, for example for a game running at 60fps, like we saw before, Time.deltaTime would report 0.01666.Let's look at the formula for Lerp() again and try to simulate how that like of code suggested by the reader would work:current = initial + (target - initial ) * 0.01666;Yep... Every single frame that this code would run, it would return an incorrect value. In fact the value would never increase or decrease.That line of code does not depend at all in time elapsed, but rather, on the current frame rate and nothing more!: FAILS, never use this!Here's a comment from another reader:Let's apply some real numbers to this, let's saystarts at 0, andwill be 1, and that the game will be running at a constant 60fps.When we run this code, we will see that even after a couple secondsstill not reachedAlso it seems to never reach it, as the more time than passes, the more slowly the value increases. By the way, that adaptionSpeed value suggested by the reader is a typical case of magical number / fudge factor, a number that is just there to force the result to the expected value, since the value ofhas no real meaning, as even changing it from 1 to 2 does not make the lerp reach the target in twice the speed.: Well it "works", but only use this in situations where accuracy / reaching the target does not matter, and where you don't need exactly control of the lerp duration (in seconds).We should not call this an lerp actually, but more like an pseudo "exponential easing", where you have a feedback loop with decaying acceleration.One use case for this would be "lerping" from a current value to a target that is constantly changing (eg: having an enemy chasing the player position).In the last section of this article I'll go into proper "easing" functions that actually can lerp from A to B in a pre-determined duration in seconds.That's why I usually recommend staying away from this, never pass Time.deltaTime directly to Lerp unless it's a quick and dirty script for game jams or you actually know what you are doing and the consequences of doing it.If you want to have a very polished game, then 90% of your game code will be Lerps!With a bit of imagination, you can use it anywhere. Check the "Art of Screenshake" video for some inspiration!Here's some examples. Let's say you want to animate some variable based on time. It could be the movement of a character, the fill value of an health bar, the transparency of a text or basically anything else in a game that has a visual or logical representation.Here's a very simple code pattern I like to use, that could be used for animating anything.It includes a clearly defined start and end of animation, which you could use to trigger other animations / events.By the way, let's make sure this is understood, while here I calculate the interpolation value based on time, it all depends on what you need, that value can come from anywhere, a variable, player input, a file, etc.Alternatively, instead of using a variable to store the initial time, you can use a variable that starts at zero and every frame acumulates the current Time.deltaTime .This will give you basically the same result, and for most developers this will be the prefered way, but if you need the animation to have a specific duration it might not be intuitive for less experienced developers.Like we saw before, Lerp() is used to interpolate two values.But sometimes we have 3 values, what can be done about it?This depends a bit on what exactly you want to do, but let's say you have values A, B and C and what to interpolate in that order, meaning that at 0 you have A, at 0.5 you have B and at 1 you have C. We can solve that by separating it in two Lerps.You could do the same for 4 or more values if you follow the same logic and divide them into multiple ranges / lerps.Also it would be possible to define different ranges, like the middle value be at percentage 0.25 instead of 0.5, etc.Lerp() can be extended to support vectors, meaning you can use it too calculate a Vector that is a mix of two other vectors.This is very simple and just requires you to Lerp each vector component individually.Eg:myVector.x = lerp(a.x, b.x, t);myVector.y = lerp(a.y, b.y, t);To support 3 dimensions, just interpolate the z component also. With higher dimensions it works the same, just interpolate all components.And with colors, is the same, we just need to think of them as vector with 4 components (red, green, blue and alpha).By the way, if you're using Unity, there's Vector2.Lerp() Vector3.Lerp() and Color.Lerp() which already do this for you.Lerp is a mathematical function that can be inverted easily.This can be useful if you have three values, A, B, C, you know that C is somewhere between A and B, and you want to know what value between 0 and 1 will interpolate A and B to get C.Wait, what?Let's speak about a more concrete example.Imagine you have a game with some kind of magical bar, represented by a variable that always goes from 20 to 100. However for displaying purposes, you want to show a progress bar / percentage instead of the real value.You can just do:percent = InverseLerp(20, 100, currentValue).(This gives a percent in the 0 to 1 range, if you need it in in range 0 to 100, of course, just multiply the result by 100!)If you're using Unity, you have access to Mathf.InverseLerp() , otherwise, if you need it for some reason, here's the formula for it.InverseLerp(a, b, c) = (c - a) / (b - a);In some situations, you might need to do an InverseLerp in 2D, meaning, you have vectors A and B, plus C which is a vector somewhere in the middle of A and B.Here, Unity can't help you, since there's no Vector2.InverseLerp!And if you try to write your own, you might understand that it is not simple like a 1D InverseLerp.Here's a simple solution that will work, given that point C actually lies in a line between A and B (meaning, they are colinear points).InverseLerp2D(a, b, c) = Distance(C, A) / Distance( B, A)Wait, this seems very similar to the formula to InverseLerp1D!By the way, you can do InverseLerp3D using the same formula, however in that case you have a point and a plane, instead of you should guarantee that all 4 points are coplanar A bilinear lerp is an advanced lerp that takes 6 parameters instead of 3.Imagine a square area with points A, B, C and D.And now you want to calculate a value somewhere in the middle of the four points (anywhere in the grey square), by specifying two interpolation values (X and Y).Here's some examples of problems that can be solved by Bilinear Lerps:For advanced developers, BilinearLerp can be a very useful tool. So how you implement it in code?Since you have two interpolation variables, X and Y, you have to first interpolate the points using X, and then interpolate the result of that using Y.AC = Lerp(A, C, Y);BD = Lerp(B, D, Y);Result = Lerp(AC, BD, X);Hey, its actually that simple!In the previous topic, note that we are assuming Lerping() over a flat surface. But there are situations where you might want to interpolate 4 points that are not necessarly co-planar.For example, if you are lerping over 2 triangles generated from an heightmap, they might not be co-planar, and in that case, you might want to resort to a different approach.One way is to detect from which of the 2 triangles the target point falls two, and then use Barycentric interpolation, which we can consider a special case of linear interpolation.Imagine a triangle formed by points A, B and C, for each of those points you can have 3 weights (sA, sB, sC) than we summed together we get 1.This kind of setup allows us to interpolate between 3 points by specifying 3 interpolation values ranging from 0 to 1, with a simple formula:barycentricInterpolation(A, B, C, sA, sB, sC) = A * sA + B * sB + C * sCThe opposite can also be done, if you only have the Target point and you want to find the weights (sA, sB, sC). This is specifially useful if you are doing raycasting hit tests, and want to find out where exactly a ray collided with a triangle.One way to calculate the weights is to calculate the distance from the target point to A, B and C, then divide each distance by the sum of the 3 distances.Some application area for this include:You might already heard about Trilinear texturing, which consists of the GPU taking 2 mipmaps and doing a BilinearLerp of them (where each mipmap consists of a BilinearLerp over the pixels). And each of those BilinearLerps have Lerps inside them, its Lerps all the way down!First, you might be interested to learn some places where a TrilinearLerp might be useful.I think the chances of you needing to do Trilinear Filtering in the CPU are slim (unless you are writing a software rasterizer!). However there are some situations where it might be useful.There's no point in going to 4 dimensions, as that would mean interpolate 16 corners of an hypercube using 4 separate interpolation values. Unless you are building a timemachine or something, you will not need it, trust me!Ok, I was talking about how 4 dimensions being overkill, but there is a special case, Quaternions, which are used to represent rotations in 3D.Unity includes both Quaternion.Lerp and Quaternion.Slerp. Both take two quaternions and one interpolation value as input. To understand the difference between them, take a look at this video Ideally you want to use Slerp instead of Lerp, as it gives more accurate interpolations, however it is a bit more computational expensive compared to Lerp, as it requires more calculations.Ok, for many guys reading this, this might be the only part that interests you.While a LERP is nice and basic, if you do animations with a LERP, they will be, well, linear, meaning that the values will go from A to B in constant time, no acceleration at all!Those with artistic blood in their veins may prefer something more exquisite, like having the values start slow from A and the accelerate fast when near B. Or having the values bounce a bit before reaching B. And luckily, there's an easy way to do that!There are a couple (well, a lot!) of equations generally called "Easing Equations", which seem to have been popularised by a guy called Robert Penner You can find a nice page with pictures of them here Each equation has a separate formula, that takes a value from 0 to 1 and also returns a value from 0 to 1. To put it in more simple words, they can map a linear curve to a more interesting curve!And to do this in code is quite simple, before doing a Lerp, first filter the interpolation value with an Ease function.x = CalculateX(); // however you want, just make sure it is a value between 0 and 1x = EaseInSine(x); // could be any other easing function!result = Lerp(a, b, x);I'm not going to bother writing all the easing equations here, they would take lots of space, and you can easily find them in other places You can find an interesting video with many ideas how to use easings here Interpolating values is one of the most important things in game dev, especially when talking about game feel / game polish.Don't be afraid of it, try to have a good grasp of how it works and what options you have and be creative with Lerping!

Tags: