Is it me or do tutorials use lerp incorrectly?

Godot Version

4.6

Question

Am I right in thinking the usual use of lerp is incorrect?

my assumptions: lerp stands for linear interpolation, when you use it you want to linearly interpolate from the initial value when an action occurred to the final value.

For this to work on a one-off action you would need to record the start value, set the end value and then frame-by-frame increase the factor between 0 and 1, every tutorial I’ve seen for something like decreasing the brightness of something when an action is triggered they lerp on every frame based on the last frame’s value, which would e.g. at 10% per frame on 0 initial value and 100 target give 0,10, 19, 27.1,34.39,40.95 never quite reaching 100 until rounding errors happen

instead of the intuitively expected 0,10,20,30,40,50,60,70,80,90,100

Now I understand that using it the way people do works for people in practice, it’s misleading and I’ve never seen anyone point out how the usual implementation actually works.

here’s a graph, 100 iterations, assuming you never want to go past your target (extrapolate) and clamp the factor. Factor increments by 10% every iteration for the linear one and stays at 10% for the usual implementation.

4 Likes

Yes, when used iteratively like this it results in geometric progression rather than linear, because interpolation starting value is not fixed but rather moves nonlinearly (percentage of current start-end span) towards the end value with each iteration.

Precisely - it’s misleading. I pointed that out numerous times in discussions about frame-by-frame interpolation.

To get truly linear steps, using move_toward() is a viable alternative. Its step is absolute, not a percentage of the start-end span like it is for lerp().

Note that abusing lerp() like this to create a geometric progression may be what you actually want in some cases.

7 Likes

Yes I have seen this misused countless of times, no matter the engine or context.

5 Likes

Thanks! Glad to hear I’m not going nuts. When used the way taught in _process it’s also going to produce different results based upon frame rate, since it’s non-linear, even if you take into account delta.

2 Likes

Yeah the results will vary with framerate but not by much in the common fps range. Noone may actually spot the difference, which is additionally “camouflaged” by non-linearity. So it may be acceptable to just go with it when you need to produce that eased feel.

I did a benchmark in one of the older threads where this problem was tangentially discussed. The convergence time gets longer on higher frame rates, and the difference is more pronounced for larger interpolation parameters:

2 Likes

Looks like the lerp() might be a bit over damped. One step beyond critically damped mentioned in the link. The curve resembles a system response to a unit step impulse. These systems can be mechanical consisting of the springs etc or electrical, thermal …basically natural. So if you wanted a natural response to a force or something, maybe animations blending, the curve is at least realistic. Its close enough to model the critically damped configuration or slightly over damped.

Quote below:

“system is underdamped and oscillate more and more as ζ→0.

Some notes about this image (that are true as long as ζ>0):

  • Note that critical damping (ζ=1) does not cause any unexpected behavior; this just reinforces the idea that critical damping is a special case mathematically, but not in terms of the physical behavior of a system.

  • If H 0,LP≠1, the response scales with it (i.e., if H0,LP doubles, the amplitude of the response doubles; this is not shown on the graph).

  • The initial value (t=0+) is given by H(∞) so yγ(0+)=0 (you can also show that the first derivative of yγ(t) is 0 at t=0+).

  • The final value (t→∞) is given by H(0), so yγ(∞)=1.”

So to make it critically damped you could try setting up a formula given here:

But the math isnt defined for zeta == 1, so you could maybe go for close to 1. Then you could use the trick at the end of the page to set omega given the rise time of the system.

I dont really have the fine grained control over the animations so it might be worth at least figuring out how long a lerp() is going to take and tweaking the lerp factor.

@Aviator Yes, the behavior of iterative linear interpolation can mimic critically damped or overdamped step response of a system. Not sure there’s practical value in broadening the conceptual framework this much when thinking about this in context of games though.

Since iterative lerp() is used to implement geometric decay here, it will theoretically take infinite number of iterations to converge exactly. We can calculate the number of iterations needed to go below some margin of error though.

The value at nth iteration is by definition:

value_n = end - (end - start) * pow(1 - t, n)

where t is the lerp parameter.

Solve for n:

n = log((end - value_n) / (end - start)) / log(1 - t)

Once we know how many iterations it takes to reach value_n, we can divide it by frame/tick rate to get the total time needed:

duration = n / ticks_per_second

Analogously, we can solve for the lerp parameter as well, which will yield:

t = 1 - pow((end - value_n) / (end - start), 1 / (duration * ticks_per_second))

Letting as calculate the parameter needed to reach the error margin in desired time, for the given tick rate.

1 Like

Cheers for the code.

Heres what i got for the solution. A bit rusty since i havent tried to solve one of these in years.

F(n)= (P2 -f(n-1))*t + f(n-1)

F(n) = p2 t -f(n-1)t + f(n-1)
F(n )= p2 t + f(n-1)(1-t)

Swap n  for n+1

F(n+1) = (1-t)f(n) + p2 t

Sub in f(n) ...
f(n+1) = (1-t)( (1-t)f(n-2) + p2 t) + p2 t
= (1-t)^2f(n-2) + (1-t) t p2 + p2 t
= (1-t)^2 f(n-2) +( (1-t)+1) p2 t
= .... + t ^ 2 p2
... then the same until f(n-p)=f(0)=p1
Sol.
F(n+1) = (1-t)^(n+1) f(0) + p2 t^(n+1)

In fact i made a mistake, when i sub in f(n-2) second part miltiplying p2 gets complicated…

= (1-t)^2( (1-t)f(n-2) +p2 t) + p2 t^2
= (1-t)^3 f(n-2) + (1-t )^2 p2 t + p2 t^2
...( ( 1- t )^2+ t ) p2 t

So if i dont simplify the p2 t terms increase like this …

((1-t) +1)p2t + (1-t)^2 p2t
((1-t) + 1 + (1-t)^2) 

so it would be

1 + (1-t) + (1 -t)^2 + (1-t)^3 ...etc

So the solution

f(n+1) = (1-t)^(n+1) f(0) + p2 t (1 + sum( (1-t)^i, n+1))

So i finally get this from Mathworld, a well known geometric series

sum(r^k,n) = (1 - r^(n+1))/ (1-r)
Subbing 1-t
(1-(1-t)^(n+1)) / 1 -(1-t) = (1-(1-t)^(n+1))/t

Not looking at convergence the solution i get is now

f(n+1) = (1-t)^(n+1) f(0) + p2 t ((1-((1-t)^(n+1)))/t)

Edit …

simplifying ….

f(n+1)=  p2 + (1-t)^(n+1) (p1 - p2)

Now im going off to draw some game levels bye.

Already confused here so if anyone knows how the formula sorts out please explain