How Does the GLSL mix Function Work on Vectors?

In OpenGL Shading Language (GLSL), the mix function performs linear interpolation between two values based on a third interpolation factor. When applied to vector data types, mix operates component-wise according to the algebraic formula \(x \times (1.0 - a) + y \times a\). This article examines the core mechanics, signature variants, component-level execution, extrapolation behavior, and practical use cases of vector linear interpolation in GLSL shaders.

Mathematical Definition and Vector Semantics

At its core, GLSL's mix(x, y, a) evaluates a standard linear interpolation (often abbreviated as lerp). For vector types such as vec2, vec3, and vec4 (as well as integer and double variants like ivec or dvec), the operation is mapped across every individual vector channel independently:

\[result_i = x_i \times (1.0 - a_i) + y_i \times a_i\]

Where \(i\) represents each corresponding vector component (\(x, y, z, w\) or \(r, g, b, a\)).

Function Signatures for Vectors

GLSL provides two primary function signatures when mixing vector types:

1. Vector Blending with a Scalar Factor

genType mix(genType x, genType y, float a);

When the third parameter a is a single float, that same scalar weight applies to all components of vectors x and y. For example:

vec3 colorA = vec3(1.0, 0.0, 0.0); // Red
vec3 colorB = vec3(0.0, 0.0, 1.0); // Blue
vec3 blended = mix(colorA, colorB, 0.25);

The resulting vector evaluates to:

2. Vector Blending with a Vector Factor

genType mix(genType x, genType y, genType a);

When a is a vector of the same dimension as x and y, each channel receives a unique blending coefficient:

vec2 startPos = vec2(0.0, 10.0);
vec2 endPos   = vec2(100.0, 50.0);
vec2 weight   = vec2(0.5, 0.1);

vec2 result = mix(startPos, endPos, weight);
// result.x = mix(0.0, 100.0, 0.5) = 50.0
// result.y = mix(10.0, 50.0, 0.1) = 14.0
// result = vec2(50.0, 14.0)

Extrapolation and Value Range

A common misconception is that mix automatically clamps values within the range \([0.0, 1.0]\). The standard mix function does not restrict the interpolation factor \(a\):

To ensure bounded interpolation, developers frequently wrap the weight factor with clamp(a, 0.0, 1.0) or saturate(a).

Boolean Selection Variant

Starting in GLSL 1.30 and GLSL ES 3.00, mix includes an overload that accepts a boolean vector (e.g., bvec2, bvec3, bvec4) for the selector argument:

genType mix(genType x, genType y, genBType a);

In this mode, mix behaves as a conditional component selector rather than an arithmetic interpolator:

This allows conditional vector masking on the GPU without dynamic branching penalties.