These last years, in the context of working on a graphic rendering engine, I've been confronted to many engineering problems involving mathematics: basic geometry, colorimetry, curves, animations, video, etc. In all these situations, I needed at some point to formalize a "space conversion" of some sort.
My mathematical background is extremely fragile, but coming up with some very basic tooling for these operations helped me building up my mental models so much faster. These formulas were by far the most impactful in my daily struggle with maths.
Formula 1: from normalized input to whatever output
Anytime you try to abstract a concept mathematically, you're usually working
within the [0;1]
range, the "purest" data form representation. This is often
referred as the "normalized form". When we have a value in this range, we can
map it to any range using a mix()
function, also known as lerp()
:
The function is known to have 2 forms, both having their use-case:
mix(a,b,x) = a*(1-x) + bx
mix(a,b,x) = x*(b-a) + a
The first one is numerically stable, meaning that's the form we usually want to use in the code, because it will lead to less floating point inaccuracies.
But the 2nd one is also useful because it does not repeat the x
input. I find
it handy to work with when doing substitution in basic algebra. It also notably
follows the well-known polynomial form. Finally, It's also easier to
reason with: b-a
represents the total distance ("end minus start"), + a
represents the offsetting, and x
represents the "amount" of the distance.
Out of all the formulas in the article, this is the most popular one. It's often referred as the "linear interpolation", but I don't like this naming because it directly conflicts with the 2nd formula doing the exact opposite.
It's so popular that GLSL does have it builtin. The wide variety of applications makes it impossible to make an exhaustive list, but if I had to pick one example it would be the mix of two colors when doing a gradient: if you want a certain percentage of a color, and the remaining of another color, this is what you will use (let's ignore gamma correction for now, alright?).
Formula 2: from whatever input to normalized output
This 2nd formula is also a "linear interpolation", except it's the exact
opposite: we're going from an arbitrary range to a normalized one. The use of
the same term to refer to both functions was a huge source of confusion for me
in the past. Since this function has no official name, I will simply refer to
it as linear(a,b,x) = (x-a)/(b-a)
, or sometimes linear_norm()
. Some people
also like to call it "inverse lerp":
This complements mix()
in such an elegant way, it's a real shame it has no
"official" name. The usefulness of this function is basically to provide a way
of normalizing a value from any space/range. Said differently, this function is
a way of getting a standard and re-usable answer to the question "where is x
located between a
and b
?"
Formula 3: From whatever input to whatever output
Now with the help of mix()
and linear()
we can now go from one range to any
other: remap(a,b,c,d,x) = mix(c,d,linear(a,b,x))
.
The logic is pretty straightforward: we have a value in the range [a;b]
,
which we first normalize with linear()
, then we directly use that result to
signify where we want to land on [c;d]
using mix()
.
If you develop the expression, you will get the following useful polynomial
form: remap(a,b,c,d,x) = x*(d-c)/(b-a) + c-a*(d-c)/(b-a)
.
Step and non-linear variants
You may see linearstep()
in the wild, which is simply linear()
with a [0;1]
clamping: linearstep(a,b,x) = saturate(linear(a,b,x))
(which
is useful in case x
is outside [a;b]
).
If you've worked with GLSL, you might have also heard of
smoothstep()
. Instead of being linear, it follows a curve, but
in its implementation it's using linearstep()
(hence the "step"), then bends
the results ("smooth") using a Hermite polynomial:
smoothstep(a,b,x) = 3t²-2t³
with t = linearstep(a,b,x)
Bonus: relationship between these formulas
If like me you're wondering why mix()
and linear()
look so different even
though their concept is so close, following is a demonstration for going from
one formula to another with basic algebra:
mix → linear
y = a*(1-x)+bx
y = a - ax + bx
y = a + bx - ax
y-a = x * (b-a)
x = (y-a)/(b-a)
linear → mix
x = (y-a)/(b-a)
x*(b-a) = y-a
bx - ax + a = y
y = a*(1-x)+bx
Conclusion
I'm sure many of the people reading this will sigh at such elementary maths,
but nowadays, not a week passes without me looking at the visual representation
of mix()
and linear()
when building various mental models for the concepts
I work with. I'm hoping this will be useful to other people.