Skip to content
Fundamental · ShapingBeginner

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

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 UV height as t, 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