Mix, clamp & remap
Blend between two values, keep a value in range, and move it from one range to another.
How it works
Most shader math ends up moving numbers between ranges: a 0–1 coordinate becomes a color, a -1..1 wave becomes a brightness, a distance becomes an opacity. Three small operations do nearly all of it.
mix: from a to b
UV coordinates A 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.mix(a, b, t) is linear interpolation, called lerp in HLSL and most node editors. It returns a when t is 0, b
when t is 1, and the straight line between them in between: a * (1.0 - t) + b * t. It works on floats and on
whole vectors, so it blends colors as easily as numbers. With the UVA 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.uv = gl_FragCoord.xy / resolutiont, two colors
make a sky:
vec3 horizon = vec3(0.95, 0.55, 0.3);
vec3 zenith = vec3(0.2, 0.35, 0.8);
vec3 sky = mix(horizon, zenith, uv.y); // a vertical gradient
t can also be a vector, which blends each component by its own amount. And a t outside 0–1 doesn’t stop at the
ends: mix keeps going along the same line (it extrapolates), which is sometimes useful and often a bug.
clamp: stay in range
clamp(x, lo, hi) limits x to the range from lo to hi; it is min(max(x, lo), hi). Clamping to 0–1 is so
common that HLSL and node editors have a name for it, saturate. Clamp before a mix when t might leave its range,
and before passing a value that must not be negative, such as the base of pow.
remap: from one range to another
GLSL has no remap function, but it takes two steps. First undo a mix: given a value x between a and b, find how
far along it is. This is the inverse lerp:
float t = (x - a) / (b - a); // 0 at a, 1 at b
Then feed that t to a mix toward the new range:
float remap(float x, float inLo, float inHi, float outLo, float outHi) {
float t = clamp((x - inLo) / (inHi - inLo), 0.0, 1.0);
return mix(outLo, outHi, t);
}
The clamp makes values outside the input range stick to the ends; leave it out when the line should continue. The
input range must not be empty: when inLo equals inHi the division is by zero.
One special case is worth knowing by heart. x * 0.5 + 0.5 takes a -1..1 value, such as sin() or a centered
coordinate, into 0..1, and x * 2.0 - 1.0 goes back.
Further reading
- mix (opens in a new tab)Reference
- clamp (opens in a new tab)Reference
- Linear interpolation (opens in a new tab)Reference