![]() |
|
News - Blog: Here’s an easy way to improve lerp smoothing - Printable Version +- Sick Gaming (https://sickgaming.net) +-- Forum: Sick Gaming Community (https://sickgaming.net/forum-1.html) +--- Forum: Lounge (https://sickgaming.net/forum-8.html) +--- Thread: News - Blog: Here’s an easy way to improve lerp smoothing (/thread-84567.html) |
News - Blog: Here’s an easy way to improve lerp smoothing - xSicKxBot - 04-09-2018 Blog: Here’s an easy way to improve lerp smoothing The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community. The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company. A Useful Snippet: A lot of game developers will instantly recognize this line of code: Code: value = lerp(value, targetValue, 0.1)It’s super useful, and can be used for all sorts of things. With a higher constant, it’s a good way to smooth out jittery input from a mouse or joystick. With a low constant, it’s a nice way to smoothly animate a progress bar or a camera following a player. It will never overshoot the target value even if it’s changing, and it changes the speed based on how far away it is so it will always quickly converge on the target. Pretty good for a one liner! BUT! Unfortunately it has a couple problems that are often ignored to some extent: The first is that it’s highly dependent on frame rate. The common advice is to use a fixed time step, but that’s not great advice. If it’s for player or camera animation, you will likely end up with visible jitters. If you change your fixed time step, you need to retune. The second is that the Code: lerp()Code: lerp()Code: lerp()Code: lerp()Fortunately, with a little math, both issues are easy to fix! Improved Version: The key is to replace the simple Code: lerp()Code: exp2()Code: value = lerp(target, value, exp2(-rate*deltaTime))In this version, Code: rateEven better, it’s frame rate independent. If you Code: lerp()If you are worried about the performance, don’t be. Even mobile CPUs have instructions to help calculate exponents and logarithms in a few CPU cycles. In 2018, you should be much more worried about cache misses than math lib costs, but that’s a post for another day. Conversion: So this new version is frame rate independent, and easier to tune. How do you convert your old Code: lerp()Code: rate = -fps*log2(1 - coef)Recalculate them and don’t look back! Going Further: Adjusting the numbers by hand is much easier now, but it’s still not slider friendly. Negative rates cause the math to blow up, and slowing the rate down will be very sensitive. Just add more exponents! Code: rate = exp2(logRate)value = lerp(target, value, exp2(-rate*deltaTime))This will make sliders more intuitive. Moving the slider a certain amount will always change the rate by the same fraction, and you can precompute Code: rateWhy Does It Work? Say you are lerping by 0.9 each frame. That means you are leaving Code: (1 - 0.9) = 0.1 = 10%Code: (1 - 0.9)*(1 - 0.9) = 0.01 = 1%Code: (1 - 0.9)*(1 - 0.9)*(1 - 0.9) = 0.001 = 0.1%.Code: (1 - 0.9)^nSo really all you are doing by repeatedly lerping is evaluating an exponential curve, and that’s why you can replace it with an actual exponential curve based on time. As an interesting aside, since you are moving partway towards the targe value each frame, you’ll never actually arrive at the target… or will you? Code: floatCode: doubleCode: value = 0.0target = 1.0alpha = 0.9100.times do|i| value = (1 - alpha)*value + alpha*target puts "#{i + 1}: #{value == target}"endAnd the output? Code: 1: false2: false... (more false values)15: false16: false17: trueIt shouldn’t be too surprising that precision runs out after the 16th iteration. Code: (1 - 0.9)^17Code: 1 - 1e-17With constants less than 0.5 the pattern changes and something curious happens. Say you keep lerping with a constant of 0.5. Eventually, you will run out of precision and the next possible floating point number after Code: valueCode: targetCode: targetCode: value |