How Does texture() Sample 2D Colors in GLSL?
In OpenGL Shading Language (GLSL), the texture()
function retrieves a color from a texture sampler using normalized 2D
coordinates. This article covers the step-by-step pipeline executed by
the GPU during sampling: transforming normalized UV coordinates,
applying boundary wrapping modes, calculating mipmap levels of detail
using screen-space derivatives, filtering adjacent texels through
nearest-neighbor or bilinear interpolation, and returning the resulting
color vector.
Normalized Texture Coordinates and Space Mapping
The texture() function typically accepts a
sampler2D and a vec2 representing UV texture
coordinates. Unlike pixel grids that operate in integer space, standard
texture coordinates are normalized to the floating-point range of [0.0,
1.0].
When the GPU evaluates coordinates:
- The coordinate \((0.0, 0.0)\) corresponds to the bottom-left corner of the texture image.
- The coordinate \((1.0, 1.0)\) corresponds to the top-right corner.
- The hardware multiplies the normalized coordinates by the actual texture dimensions (width and height in texels) to locate the exact continuous position on the texture surface.
Wrapping and Address Handling
If UV coordinates fall outside the standard [0.0, 1.0] range, the texture sampling hardware applies the wrapping parameters configured on the sampler object or texture unit:
- GL_REPEAT: Ignores the integer part of the coordinate, creating a repeating pattern.
- GL_MIRRORED_REPEAT: Repeats the texture while reversing the direction at every integer boundary.
- GL_CLAMP_TO_EDGE: Clamps the coordinates between \(0.0\) and \(1.0\), stretching the edge texels indefinitely.
- GL_CLAMP_TO_BORDER: Samples a user-defined border color when coordinates exceed the boundary.
Level of Detail and Mipmap Selection
Before blending color values, the GPU determines which mipmap level to sample. Sampling high-resolution textures on small screen surfaces causes aliasing artifacts.
GLSL automatically calculates the rate of change of texture
coordinates across adjacent screen pixels using implicit partial
derivatives (dFdx and dFdy):
- The hardware compares the UV coordinates of adjacent fragments within a 2x2 fragment quad.
- The magnitude of change determines the level of detail (\(\lambda\), or LOD).
- If a fragment covers multiple texels, the GPU selects a smaller, pre-filtered mipmap level or blends between two adjacent mipmap levels (trilinear filtering).
Texel Filtering and Interpolation
A continuous coordinate rarely aligns precisely with the center of an
individual discrete texel. The sampler applies the configured
minification (GL_TEXTURE_MIN_FILTER) or magnification
(GL_TEXTURE_MAG_FILTER) algorithm:
- Nearest-Neighbor Filtering
(
GL_NEAREST): Selects the single texel whose center is closest to the calculated coordinate. This produces sharp, blocky edges when viewed closely. - Linear Filtering (
GL_LINEAR): Identifies the four texels surrounding the coordinate and computes a weighted bilinear average based on the fractional distance to each texel center.
When mipmapping is enabled, linear filtering can also interpolate between the results of two adjacent mipmap levels to produce seamless transitions.
Returning the Color Vector
Once filtering is complete, the GPU formats the sampled texel data
according to the internal format of the texture (such as RGB, RGBA, or
single-channel luminance). The texture() function returns
this result as a standard vec4 containing red, green, blue,
and alpha components, making it immediately available for lighting,
shading, and composite calculations within the fragment shader.