Sampling 3D Textures with sampler3D in GLSL
This article explains how to sample 3D volumetric textures in the
OpenGL Shading Language (GLSL) using the sampler3D uniform
type. It covers the declaration of 3D samplers, the structure of
three-dimensional texture coordinates, the execution of the standard
texture() lookup function, hardware-level trilinear
filtering, and practical shader code implementation for rendering
volumetric data.
Declaring the 3D Sampler
To sample a 3D texture in GLSL, you must first declare a uniform
variable of type sampler3D in your shader, typically inside
the fragment shader:
#version 330 core
out vec4 FragColor;
uniform sampler3D u_VolumeTexture;This uniform acts as a handle to the 3D texture unit bound on the CPU
side via the host graphics API (such as
glBindTexture(GL_TEXTURE_3D, textureID) in OpenGL).
3D Texture Coordinates
Unlike 2D textures that rely on two coordinates (u, v),
volumetric textures require three-dimensional normalized coordinates
(s, t, r) or (x, y, z) passed as a
vec3.
Each component corresponds to an axis in the volume:
- s (x): Horizontal axis across the volume width.
- t (y): Vertical axis across the volume height.
- r (z): Depth axis across the volume layers or slices.
These coordinates normally span the normalized range from
0.0 to 1.0. A coordinate of
vec3(0.5, 0.5, 0.5) targets the exact center of the 3D
voxel grid.
The Sampling Function
In modern GLSL (version 3.30 and later), 3D textures are sampled
using the overloaded texture() function. The function takes
the sampler3D as the first argument and a vec3
representing the coordinates as the second argument:
vec4 texel = texture(u_VolumeTexture, texCoords);In legacy GLSL (version 1.20 or lower), the dedicated function
texture3D() was used instead, but this has been deprecated
in favor of the unified texture() function.
Basic GLSL Fragment Shader Example
Below is a complete fragment shader demonstrating how to sample a volumetric texture:
#version 330 core
in vec3 v_TexCoord; // Interpolated 3D coordinate from the vertex shader
out vec4 FragColor;
uniform sampler3D u_VolumeTexture;
void main()
{
// Sample the 3D texture
vec4 volumeColor = texture(u_VolumeTexture, v_TexCoord);
// Output the resulting color
FragColor = volumeColor;
}Hardware Filtering and Trilinear Interpolation
When texture() is called on a sampler3D,
the GPU handles coordinate mapping and filtering based on the sampler
parameters configured in the host application:
- Trilinear Filtering: If the minification and
magnification filters are set to
GL_LINEAR, the GPU performs hardware-accelerated trilinear interpolation. It calculates the weighted average of the eight nearest voxels surrounding the sampling coordinate across all three dimensions. - Coordinate Clamping: Wrapping modes such as
GL_CLAMP_TO_EDGEorGL_REPEATapply across all three axes (GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T, andGL_TEXTURE_WRAP_R), preventing artifacts at the volume boundaries.
Fetching Exact Voxels with texelFetch
If you need to bypass normalized coordinates and filtering to read an
exact voxel from the volume grid, GLSL provides the
texelFetch() function. This function uses integer
coordinates (ivec3) and an explicit mipmap level:
ivec3 voxelCoord = ivec3(gl_FragCoord.xy, 10); // Target slice 10
int lod = 0;
vec4 exactVoxel = texelFetch(u_VolumeTexture, voxelCoord, lod);Using sampler3D alongside texture() or
texelFetch() provides efficient, hardware-accelerated
access to voxel grids, making it fundamental for medical imaging,
volumetric cloud simulation, and 3D lookup tables (LUTs).