How Does textureGrad Work in GLSL?

The textureGrad function in GLSL provides explicit control over texture sampling by allowing developers to manually specify screen-space partial derivatives (\(\partial P/\partial x\) and \(\partial P/\partial y\)) instead of relying on automatic hardware evaluations. This capability resolves critical visual artifacts—such as mipmap selection errors along texture coordinate discontinuities—and permits texture sampling within non-uniform control flow where automatic derivative calculation produces undefined behavior.

Automatic Derivatives vs. Explicit Gradients

Under standard execution, fragment shaders evaluate functions like texture() by computing implicit screen-space derivatives across \(2 \times 2\) pixel quads. The GPU measures the rate of change of texture coordinates between adjacent pixels using functions analogous to dFdx() and dFdy(). These partial derivatives dictate the level of detail (LOD) and the appropriate mipmap level required to minimize aliasing while preserving sharpness.

When texture coordinates change abruptly—such as across UV seams, wrapped coordinates, or atlas boundaries—implicit derivative calculations compute an artificially massive rate of change. The hardware interprets this jump as extreme minification and selects the lowest-resolution mipmap, causing visible blurred lines along the seam.

Syntax and Operation of textureGrad

The textureGrad function bypasses implicit quad comparisons by accepting user-provided gradient vectors:

vec4 textureGrad(
    sampler2D sampler,
    vec2 P,
    vec2 dPdx,
    vec2 dPdy
);

By supplying custom vectors for dPdx and dPdy, the hardware derives the footprint of the pixel in texture space directly from your calculations, ignoring the coordinates evaluated in neighboring fragments of the \(2 \times 2\) quad.

Primary Use Cases

Fixing UV Discontinuities and Seams

When wrapping textures around spheres, cylinders, or computing continuous fractals, coordinates often wrap from \(1.0\) back to \(0.0\). Evaluating derivatives across the seam yields a derivative magnitude near \(1.0\) instead of \(\approx 0.0\). Computing gradients prior to wrapping or filtering out the wrap step with fract() preserves continuous gradients:

vec2 uv = rawUV;
vec2 dx = dFdx(uv);
vec2 dy = dFdy(uv);

// Apply wrapping or atlas clamping
vec2 wrappedUV = fract(uv);

// Sample using the pre-wrap derivatives
vec4 color = textureGrad(u_Texture, wrappedUV, dx, dy);

Dynamic Branching and Non-Uniform Control Flow

Standard GLSL texture lookups require uniform execution across all four pixels in a quad. If fragments within the same quad execute divergent branches, hardware derivatives become undefined. textureGrad enables safe texture lookups inside non-uniform conditional branches by computing the derivatives before entering the branch and passing them explicitly into the sampling call.