GLSL Texture Array Layer Interpolation
By default, GLSL and GPU hardware do not interpolate between adjacent
layers of a 2D texture array; the third coordinate is rounded to the
nearest integer slice. To achieve continuous interpolation across layers
with a floating-point index, you must implement manual trilinear
filtering by sampling the two adjacent layers and applying linear
interpolation (mix) between the results.
The Hardware Behavior of 2D Texture Arrays
Standard 2D texture arrays (sampler2DArray) are designed
to hold independent 2D slices that share the same dimensions and format.
When sampling with a coordinate vec3(u, v, layer):
- The
(u, v)coordinates undergo standard bilinear filtering across the 2D plane ifGL_LINEARis enabled. - The
layercoordinate does not undergo linear filtering. Hardware determines the layer using nearest-neighbor selection: \(layer_{index} = \lfloor layer + 0.5 \rfloor\).
Because hardware does not support filtering across the array index, seamless blending requires an explicit algorithm in your shader.
The Algorithm: Manual Trilinear Filtering
The algorithm used to blend layers smoothly is a hybrid trilinear interpolation: the hardware handles bilinear filtering on each 2D slice, while GLSL performs linear interpolation (lerp) between two separate slice lookups.
Step-by-Step Implementation
- Calculate Adjacent Layer Indices: Compute the base
layer index using
floor()and the next sequential layer index. - Determine the Fractional Blend Factor: Calculate
the interpolation weight using
fract(). - Clamp Indices: Ensure layer indices stay within the
bounds of the array (
0tototal_layers - 1) to prevent invalid memory reads. - Sample Both Slices: Perform two texture lookups at the respective integer layers.
- Linearly Interpolate: Blend the two color samples
using the GLSL
mix()function.
GLSL Example
uniform sampler2DArray u_TextureArray;
uniform float u_MaxLayers; // Total slices minus 1
vec4 sampleTextureArrayTrilinear(vec2 uv, float floatLayer) {
float layer0 = floor(floatLayer);
float layer1 = layer0 + 1.0;
float blendFactor = fract(floatLayer);
// Clamp layer indices within valid array bounds
layer0 = clamp(layer0, 0.0, u_MaxLayers);
layer1 = clamp(layer1, 0.0, u_MaxLayers);
// Hardware handles bilinear filtering on (u, v)
vec4 sample0 = texture(u_TextureArray, vec3(uv, layer0));
vec4 sample1 = texture(u_TextureArray, vec3(uv, layer1));
// Linear interpolation between the two slices
return mix(sample0, sample1, blendFactor);
}Alternative: 3D Textures
If your application requires frequent, native volumetric filtering
across three dimensions without executing two dependent texture fetches
per fragment, consider using a 3D texture (sampler3D).
Unlike 2D texture arrays, 3D textures execute true trilinear filtering
across the \(U\), \(V\), and \(W\) axes directly in hardware.