Basics, part 5

How lerp, inverse lerp and remap blend numbers

Slide from one value to another with lerp, find where a value sits with inverse lerp, and move numbers from one range into another with remap.

  • Part 56 min dose
  • Beginner

So much of what a shader does is blending. One color melts into another, a glow fades out, a slider becomes a size. Three tiny formulas do most of that work: lerp, inverse lerp and remap. Let’s meet all three, plus saturate, the little guard that keeps them in line.

Drag along the color strip
Drag the marker, or press Tab to reach it and use the arrow keys

Turn on JavaScript to run this demo.

The color at t = 0, on the left.
The color at t = 1, on the right.
lib.hlsl and strip.hlsl draw the demo above
Fig. 1

Drag the marker along the strip. The circle shows the color at the marker, and the number under it is how far along you are, from 0 to 1. Lerp calls that t. Then pick your own Color a and Color b.

From a to b with lerp

Lerp is short for linear interpolation, which is a long name for a short slide. You give it a start aa, an end bb and an amount tt:

lerp⁡(a,b,t)=a+(b−a) t\operatorname{lerp}(a, b, t) = a + (b - a)\,t

b−ab - a is the whole way from aa to bb, and tt says how much of that way to go. At t=0t = 0 you stay at aa, at t=1t = 1 you arrive at bb, and t=0.5t = 0.5 lands halfway. WebGL’s shader language, GLSL, calls it mix (lesson 6 compares the two).

Colors work the same way. A color is three numbers, one each for red, green and blue, and lerp slides each one on its own. Each column of the strip above is a lerp from mint to pink with its own tt. Halfway, mint’s extra green and pink’s extra red even out into a soft grey.

Asking backwards with inverse lerp

Sometimes you know the value and want the amount. Where does xx sit on the way from aa to bb? Turn the lerp around and solve for tt:

t=x−ab−at = \frac{x - a}{b - a}

x−ax - a is how far xx has come from the start, and b−ab - a is the whole way, so tt is the share of the way behind you. Say a range runs from 2 to 6 and xx is 5: t=(5−2)/(6−2)=0.75t = (5 - 2) / (6 - 2) = 0.75, three quarters of the way.

Unity’s shader language has no inverse lerp function, so shaders write the division out. There’s one trap. When aa equals bb, you divide by 0 and the result breaks. Keep the two ends apart.

Remap is inverse lerp, then lerp

Put the two in a row and a number can move from one range into another. Inverse lerp finds where xx sits between aa and bb, and lerp puts that same tt between cc and dd:

t=x−ab−ay=lerp⁡(c,d,t)\begin{aligned} t &= \tfrac{x - a}{b - a} \\ y &= \operatorname{lerp}(c, d, t) \end{aligned}

That’s remap. A value 40% of the way from aa to bb comes out 40% of the way from cc to dd. Make dd smaller than cc and the result runs backwards, a handy way to flip a fade.

Lerp, inverse lerp and remap

Turn on JavaScript to run this demo.

Where the old range starts.0.20
Where the old range ends.0.60
Where the new range starts.0.40
Where the new range ends.0.90
Holds t between 0 and 1.
Code for the plot above
  • f1(x):lerp(_A, _B, keep(x))
  • f2(x):keep((x - _A) / (_B - _A))
  • f3(x):lerp(_C, _D, keep((x - _A) / (_B - _A)))
Fig. 2

Mint is lerp, pink is inverse lerp and butter is remap. Pink crosses 0 at Start a and 1 at End b, and butter climbs from Start c to End d between the same guides. Mint and pink are mirror images across the diagonal from (0, 0) to (1, 1), because one undoes the other.

Keeping t between 0 and 1

Here’s the catch. Inverse lerp doesn’t stop at the ends. Past bb, tt climbs above 1, and before aa it drops below 0. Lerp then keeps going too, and you get a color beyond the end color, a size below zero or a brightness over the top. Saturate, which you met in the first lesson, holds a number between 0 and 1:

saturate⁡(t)=min⁡(max⁡(t,0),1)\operatorname{saturate}(t) = \min(\max(t, 0), 1)

Max lifts anything below 0 up to 0, and min pulls anything above 1 down to 1. Most remaps want it between their two steps, so yy stays between cc and dd. Turn on Saturate in the plot above and watch the pink and butter lines go flat past the guides, while mint does the same past 0 and 1. Leave it off when a value should keep going, like a ramp that keeps rising.

Fading with distance

Now for a classic, things that fade as they get further away. In the demo below, xx is each dot’s distance from the viewer, measured as the length of the arrow between them (see the vectors lesson). Near is aa and Far is bb. Inverse lerp and saturate give a tt that is 0 up close and 1 far away. Then that one tt drives two lerps, one for the dot’s size and one for its color. That makes two remaps from one tt.

There’s a softer cousin. Smoothstep is saturate and inverse lerp with the corners rounded off, so a fade eases in and out. The smooth edges lesson takes it apart.

Dots that fade with distance
Drag the viewer, or press Tab to reach it and use the arrow keys

Turn on JavaScript to run this demo.

Closer than this, a dot is as big and bright as it gets.0.40
Further than this, a dot is as small and dim as it gets.1.00
How the distance turns into t.
fade.hlsl draws the demo above
Fig. 3

Drag the viewer. Inside the mint ring the dots are big and butter, past the pink ring small and lilac. With Blend set to No saturate, near dots swell and turn a hotter yellow, and far ones shrink to nothing. Then try Smoothstep, which eases the fade in and out at the rings.

In practice

You’ll meet these in almost every shader:

  • Blending colors, for a tint, a shadow color or day turning into night.
  • Fading things with distance, like details that only show up close or a glow that softens far away.
  • Stretching a 0 to 1 slider, which is friendly to set and to animate, into whatever range the math needs.

Here’s a small example of my own:

// A 0 to 1 slider becomes a glow strength from 0.5 to 8.
float glow = lerp(0.5, 8.0, _Glow);
float3 col = _Color.rgb * glow;

// 0 closer than 2 metres from the camera, 1 beyond 5 metres.
float dist = distance(_WorldSpaceCameraPos, i.worldPos);
float fade = saturate((dist - 2.0) / (5.0 - 2.0));
col = lerp(col, _FogColor, fade);

_Color, _Glow and _FogColor are material settings, one Unity unit is one metre, and the fade is a saturated inverse lerp.

One mix-up catches everyone once: lerp(a, b, t) takes an amount, but smoothstep(a, b, x) takes a value and works out the amount itself.

Takeaways

  • Lerp slides from aa to bb as tt goes from 0 to 1, on numbers and colors alike.
  • Inverse lerp finds the tt of a value, and remap is inverse lerp followed by lerp.
  • Saturate keeps tt between 0 and 1, so a remap stays in its new range. Smoothstep is a saturated inverse lerp with soft ends.
Search the lessons

Type any word from the lessons, like normalize or smoothstep. The start of a word is enough.