Mix, Clamp, and Friends
Blending and constraining values
The Mix Function
Linear interpolation, or "lerp" as it is known in many languages, is one of the most fundamental operations in graphics. GLSL calls it mix, and once you understand it, you will see it everywhere.
mix(a, b, t) blends between two values based on a factor t. When t is 0, you get a. When t is 1, you get b. When t is 0.5, you get exactly halfway between them.
Interactive: Blend between two values
mix(0.2, 0.8, 0.50) = 0.500
The formula is elegant:
Think of t as asking "how much of b do I want?" At t = 0, you want none of b, so you get pure a. At t = 1, you want all of b. At t = 0.7, you want 70% of b mixed with 30% of a.
Mix for Colors
Mix truly shines when blending colors. Want to transition from red to blue? Mix them. Want to create a sunset gradient? Mix multiple colors along the UV coordinate.
Interactive: Blend between two colors
Color 1
Color 2
mix(color1, color2, uv.x) blends horizontally across the gradient
In shader code:
vec3 red = vec3(1.0, 0.2, 0.2);
vec3 blue = vec3(0.2, 0.4, 1.0);
vec3 color = mix(red, blue, uv.x);The beauty of mix is that it works on any type: floats, vec2, vec3, vec4. Each component gets interpolated independently.
The Clamp Function
Values in shaders often need boundaries. A color component should stay between 0 and 1. A position might need to stay within screen bounds. The clamp function enforces these limits.
clamp(x, minVal, maxVal) constrains x to lie between minVal and maxVal. Too low? Returns minVal. Too high? Returns maxVal. In range? Returns x unchanged.
Interactive: Constrain values to a range
Gray line is identity (x). Blue line shows clamped values.
The definition is straightforward:
Clamp is essential for safety. When computing lighting, colors can exceed 1.0. When computing positions, values can drift outside valid ranges. Clamp keeps everything in bounds.
Min and Max
The building blocks of clamp are min and max, which are useful on their own.
min(a, b) returns the smaller of two values. max(a, b) returns the larger. Simple, but powerful when combined creatively.
Interactive: Compare min and max
A classic use of min is soft shadows: take the minimum of your shadow calculation and your existing light value. Max is useful for ensuring values never go negative, like max(0.0, dotProduct) for diffuse lighting.
Abs and Sign
Sometimes you only care about magnitude, not direction. abs(x) returns the absolute value, stripping away the sign. sign(x) does the opposite: it returns -1, 0, or 1 depending on whether x is negative, zero, or positive.
Interactive: Absolute value and sign
abs(x) creates a V shape. sign(x) returns -1, 0, or 1.
Abs is everywhere in shader code. Distance calculations often need absolute differences. Reflection effects use abs to mirror coordinates. The formula abs(uv.x - 0.5) creates a V-shape centered at 0.5.
Sign is useful for conditional logic without branching. Instead of an if statement, multiply by sign to flip values based on their position.
Floor and Ceil
When you need integers from floating-point numbers, floor and ceil are your tools.
floor(x) rounds down to the nearest integer. ceil(x) rounds up. These functions are crucial for grid-based effects and tiling.
Interactive: Floor vs Ceil
floor rounds down (blue), ceil rounds up (red). Filled dots are inclusive.
For example, floor(uv * 8.0) divides your coordinate space into an 8x8 grid, giving you the integer coordinates of each cell. Combined with fract, which we will explore next chapter, this becomes the foundation of procedural patterns.
Composing Functions
The real power emerges when you chain these functions together. Each one is simple, but combinations create sophisticated behaviors.
Interactive: Compose multiple functions
x
Consider this progression:
uv.xis a linear ramp from 0 to 1abs(uv.x - 0.5)creates a V shape1.0 - abs(uv.x - 0.5) * 2.0creates a tent shapesmoothstep(0.0, 1.0, 1.0 - abs(uv.x - 0.5) * 2.0)smooths the tent
Each function transforms the previous result. Learning to think in these chains is the key to shader fluency.
Common Patterns
Here are some recipes worth memorizing:
Remap a range:
// Map x from [a, b] to [c, d]
float remapped = mix(c, d, (x - a) / (b - a));Pulse between 0 and 1:
float pulse = clamp(sin(time) * 2.0, 0.0, 1.0);Mirror around center:
float mirrored = abs(uv.x - 0.5) * 2.0;Quantize to steps:
float stepped = floor(x * steps) / steps;Key Takeaways
mix(a, b, t)linearly interpolates: essential for gradients, fades, and blendingclamp(x, min, max)constrains values to a range: prevents overflow and artifactsminandmaxselect between values: useful for soft limits and conditional logicabsstrips the sign,signextracts it: tools for symmetry and conditional mathfloorandceilround to integers: foundations for grid and tile effects- Chaining simple functions creates complex behaviors