Explicit Mipmap Selection in GLSL with textureLod

In OpenGL Shading Language (GLSL), the textureLod function provides explicit control over which mipmap level a shader samples, bypassing the hardware's automatic level-of-detail (LOD) calculation. This article explains how textureLod operates in fragment shaders, contrasts it with standard derivative-based sampling, examines why it prevents artifacts in non-uniform control flows, and demonstrates its practical implementation for advanced rendering techniques like physically based rendering (PBR) and custom blurs.

Standard Sampling vs. Explicit LOD

In standard fragment shader texture lookups using texture(), the GPU automatically calculates the mipmap level using screen-space derivatives (dFdx and dFdy). The hardware inspects how fast texture coordinates change across adjacent pixels in a \(2 \times 2\) pixel quad. If the texture coordinates change rapidly across screen space, the GPU samples a higher (smaller, lower-resolution) mipmap level to reduce aliasing.

While convenient, standard sampling relies strictly on these implicit derivatives. textureLod bypasses this mechanism entirely by accepting an explicit float parameter for the LOD:

vec4 textureLod(sampler2D sampler, vec2 P, float lod);

The lod parameter directly indicates the mipmap index:

  • 0.0 corresponds to the base, full-resolution texture (Mip 0).
  • 1.0 corresponds to the first downsampled level (half resolution).
  • Fractional values (e.g., 2.5) instruct the GPU to perform trilinear filtering, linearly interpolating between mip levels 2 and 3 if supported by the sampler's minification filter configuration.

Bypassing Derivative Artifacts in Dynamic Branching

A primary technical reason to use textureLod in fragment shaders is execution inside non-uniform control flow, such as dynamic if statements or loops.

Standard texture() functions require a valid \(2 \times 2\) pixel quad to compute screen-space derivatives. If adjacent pixels within a quad follow divergent branch paths, the derivatives become undefined, leading to visual artifacts, texture bleeding, or severe performance penalties. Because textureLod does not rely on pixel quad derivatives, it can be safely executed inside divergent branches and loops without undefined behavior.

Practical Applications

1. Physically Based Rendering (PBR)

In image-based lighting (IBL), roughness determines the blurriness of specular reflections. Radiance environment maps are pre-filtered into mip chains, where each level represents an increasing roughness value. Shaders use textureLod to sample the exact pre-filtered reflection required by the material's roughness:

float maxLod = 5.0; // Total mip levels minus one
float lod = roughness * maxLod;
vec3 prefilteredColor = textureLod(environmentMap, reflectionVector, lod).rgb;

2. Manual Blurring and Post-Processing

Instead of executing multiple texture fetches to create an artificial blur, developers can intentionally sample a higher mipmap level directly using textureLod, obtaining a low-cost, hardware-filtered blur.

3. Preventing Seams in Texture Atlases

When wrapping or clamping custom coordinates manually inside a texture atlas, standard derivatives can spike at boundary seams, causing the hardware to choose the lowest-resolution mipmap and produce visible dark lines. Explicitly calculating and clamping the LOD with textureLod eliminates these boundary artifacts.

Example Fragment Shader

#version 450 core

in vec2 vTexCoord;
out vec4 FragColor;

uniform sampler2D uTexture;
uniform float uRoughness; // Normalized value between 0.0 and 1.0

void main()
{
    float maxMipLevel = 4.0;
    float targetLod = uRoughness * maxMipLevel;

    // Explicitly sample using the calculated LOD
    FragColor = textureLod(uTexture, vTexCoord, targetLod);
}

By substituting implicit derivative calculations with a fixed or mathematically derived float value, textureLod grants absolute control over texture filtering and guarantees stable sampling behavior across any execution path in the fragment shader.