How Does Clamp Restrict Ranges in GLSL?

The clamp function in the OpenGL Shading Language (GLSL) constrains numerical values to a defined minimum and maximum boundary. This article explores how clamp operates mathematically, how it handles scalar types and vector components, the available function overloads, and common shader applications such as color grading and lighting computations.

Mathematical Definition and Core Mechanics

In GLSL, the clamp function restricts an input value \(x\) so that it never falls below a lower bound minVal or exceeds an upper bound maxVal. Mathematically, it evaluates as:

\[\text{clamp}(x, \text{minVal}, \text{maxVal}) = \min(\max(x, \text{minVal}), \text{maxVal})\]

If \(x\) is less than minVal, the function returns minVal. If \(x\) is greater than maxVal, it returns maxVal. If \(x\) lies within the interval \([\text{minVal}, \text{maxVal}]\), it returns \(x\) unmodified.

float lowVal  = clamp(-0.5, 0.0, 1.0); // Returns 0.0
float midVal  = clamp(0.65, 0.0, 1.0); // Returns 0.65
float highVal = clamp(1.42, 0.0, 1.0); // Returns 1.0

Scalar vs. Vector Operations

GLSL defines clamp across floating-point, integer, and unsigned integer scalar and vector types (float, vec2, vec3, vec4, int, ivec2, ivec3, ivec4, uint, uvec2, uvec3, uvec4).

Component-Wise Vector Clamping

When applied to vectors, clamp operates independently on each component. GLSL provides two primary variations for vector clamping:

  1. Vector bounds: Each component of \(x\) is clamped between the matching component of minVal and maxVal.
  2. Scalar bounds: Each component of \(x\) is clamped against uniform scalar bounds.
vec3 rawColor = vec3(-0.2, 0.8, 1.5);

// Using scalar bounds across all components
vec3 clampedUniform = clamp(rawColor, 0.0, 1.0);
// Result: vec3(0.0, 0.8, 1.0)

// Using per-component vector bounds
vec3 minBounds = vec3(0.0, 0.2, 0.5);
vec3 maxBounds = vec3(0.5, 0.9, 1.0);
vec3 clampedPerChannel = clamp(rawColor, minBounds, maxBounds);
// Result: vec3(0.0, 0.8, 1.0)

Parameter Ordering and Undefined Behavior

According to the official GLSL specification, clamp requires minVal to be less than or equal to maxVal. If \(\text{minVal} > \text{maxVal}\), the returned value is undefined or hardware-dependent because GPUs typically implement clamp via fused instructions or a nested sequence of \(\min(\max(x, \text{minVal}), \text{maxVal})\).

Passing inverted bounds evaluates the inner \(\max(x, \text{minVal})\) first against the higher value, resulting in unexpected output:

// Problematic usage: minVal (1.0) > maxVal (0.0)
float broken = clamp(0.5, 1.0, 0.0);
// min(max(0.5, 1.0), 0.0) -> min(1.0, 0.0) -> 0.0

To prevent unexpected rendering artifacts, dynamic bounds should be sanitized using min() and max() prior to passing them to clamp.

Common Use Cases in Shaders