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.0corresponds to the base, full-resolution texture (Mip 0).1.0corresponds 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.