How Do GLSL Min and Max Functions Handle Vectors?
In the OpenGL Shading Language (GLSL), the built-in min
and max functions are designed to operate component-wise
across vector types, either comparing two vectors of matching dimensions
or comparing each element of a vector against a single scalar value.
This guide covers how both function overloads behave mathematically, how
modern GPUs execute them efficiently, practical shader use cases like
bounding and clamping, and key syntax pitfalls to avoid.
Component-Wise Vector Overloads
When you pass two vectors of identical dimensions into
min() or max(), GLSL performs an independent,
component-wise comparison across each matching coordinate (\(x\), \(y\), \(z\), and \(w\)).
For two 3D vectors \(A = (a_x, a_y, a_z)\) and \(B = (b_x, b_y, b_z)\):
min(A, B)resolves tovec3(min(a_x, b_x), min(a_y, b_y), min(a_z, b_z))max(A, B)resolves tovec3(max(a_x, b_x), max(a_y, b_y), max(a_z, b_z))
Code Example: Vector vs. Vector
vec3 a = vec3(1.0, 5.0, 2.0);
vec3 b = vec3(4.0, 3.0, 0.5);
vec3 minimumResult = min(a, b); // Returns vec3(1.0, 3.0, 0.5)
vec3 maximumResult = max(a, b); // Returns vec3(4.0, 5.0, 2.0)The resulting vector contains the individual minima or maxima per axis, rather than comparing the overall magnitudes (lengths) of the vectors.
Vector and Scalar Overloads
GLSL also provides overloads where the first parameter is a vector
and the second parameter is a single scalar of the same underlying data
type (such as float, int, or
uint). In this scenario, the scalar value is implicitly
distributed and compared against every individual component of the
vector.
For vector \(A = (a_x, a_y, a_z)\) and scalar \(s\):
min(A, s)resolves tovec3(min(a_x, s), min(a_y, s), min(a_z, s))max(A, s)resolves tovec3(max(a_x, s), max(a_y, s), max(a_z, s))
Code Example: Vector vs. Scalar
vec3 color = vec3(1.2, 0.8, -0.4);
// Clamp upper limit to 1.0
vec3 capped = min(color, 1.0); // Returns vec3(1.0, 0.8, -0.4)
// Clamp lower limit to 0.0
vec3 nonNegative = max(color, 0.0); // Returns vec3(1.2, 0.8, 0.0)Common Practical Applications
1. Manual Clamping and Color Management
While GLSL includes a dedicated clamp(x, minVal, maxVal)
function, chaining min() and max() is often
used for directional constraints:
// Ensure normal or color channels stay within [0.0, 1.0]
vec3 normalizedColor = min(max(rawColor, 0.0), 1.0);2. Axis-Aligned Bounding Box (AABB) Calculations
In ray tracing and raymarching shaders, finding slab intersections requires tracking the nearest and farthest plane boundaries across all spatial axes simultaneously:
vec3 t0 = (boxMin - rayOrigin) / rayDirection;
vec3 t1 = (boxMax - rayOrigin) / rayDirection;
vec3 tMin = min(t0, t1); // Element-wise minimum entry times
vec3 tMax = max(t0, t1); // Element-wise maximum exit times
// Reduce vector to find global intersection interval
float entryTime = max(max(tMin.x, tMin.y), tMin.z);
float exitTime = min(min(tMax.x, tMax.y), tMax.z);3. Signed Distance Functions (SDFs)
3D procedural modeling and raymarching frequently rely on
component-wise max() to compute Euclidean distances outside
rectangular prisms and bounding volumes:
float sdBox(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, max(d.y, d.z)), 0.0);
}Hardware Execution and Performance
Modern GPUs are built on Single Instruction, Multiple Data (SIMD) and
Single Instruction, Multiple Threads (SIMT) architectures. Because
min and max translate directly into dedicated
hardware-level assembly instructions (like MIN and
MAX opcodes), they execute in a single GPU clock cycle per
component with no conditional branching overhead.
Common Pitfalls
- Implicit Vector Dimension Promotion: GLSL does not
support broadcasting between vectors of different sizes. Passing a
vec2and avec3intomin()will trigger a compile-time error. - Type Strictness: GLSL requires strict type
matching. Comparing a floating-point vector (
vec3) against an integer scalar (1instead of1.0) causes a compilation failure unless explicitly cast. - Magnitude Confusion: If your goal is to find which
vector has a larger magnitude,
min()andmax()will not compute length. You must compare scalar lengths explicitly usinglength(a)andlength(b).