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:

mix(a,b,t)=a(1t)+bt\text{mix}(a, b, t) = a \cdot (1 - t) + b \cdot t

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);
glsl

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(x,a,b)=min(max(x,a),b)\text{clamp}(x, a, b) = \min(\max(x, a), b)

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.x is a linear ramp from 0 to 1
  • abs(uv.x - 0.5) creates a V shape
  • 1.0 - abs(uv.x - 0.5) * 2.0 creates a tent shape
  • smoothstep(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));
glsl

Pulse between 0 and 1:

float pulse = clamp(sin(time) * 2.0, 0.0, 1.0);
glsl

Mirror around center:

float mirrored = abs(uv.x - 0.5) * 2.0;
glsl

Quantize to steps:

float stepped = floor(x * steps) / steps;
glsl

Key Takeaways

  • mix(a, b, t) linearly interpolates: essential for gradients, fades, and blending
  • clamp(x, min, max) constrains values to a range: prevents overflow and artifacts
  • min and max select between values: useful for soft limits and conditional logic
  • abs strips the sign, sign extracts it: tools for symmetry and conditional math
  • floor and ceil round to integers: foundations for grid and tile effects
  • Chaining simple functions creates complex behaviors