Sampling 2D Texture Arrays in GLSL

Sampling layered 2D texture arrays from multi-layer render targets in GLSL involves using dedicated array samplers and a three-component texture coordinate system. This article explains how to declare the proper sampler types, configure three-dimensional coordinates where the third component represents the layer slice, choose between normalized and unnormalized fetching functions, and avoid common pitfalls when reading from layered framebuffers.

Declaring the Sampler

To read from a 2D texture array (GL_TEXTURE_2D_ARRAY) attached to a multi-layer framebuffer, declare a uniform using the sampler2DArray type in your fragment shader:

uniform sampler2DArray u_LayeredTexture;

For integer-based render targets, use isampler2DArray or usampler2DArray. For depth comparison textures, use sampler2DArrayShadow.

Coordinate Structure

Unlike standard 2D textures that require a vec2 coordinate, a 2D texture array requires a vec3:

  • Component 1 (s or u): Horizontal normalized coordinate along the target layer, typically in the range [0.0, 1.0].
  • Component 2 (t or v): Vertical normalized coordinate along the target layer, typically in the range [0.0, 1.0].
  • Component 3 (p or layer): A floating-point layer index referencing the zero-based layer slice (e.g., 0.0 for layer 0, 1.0 for layer 1).

Normalized Sampling with texture()

When using linear or nearest-neighbor filtering with normalized coordinates, call the texture() function passing the sampler2DArray and the vec3 coordinate:

in vec2 v_TexCoord;
uniform int u_TargetLayer;
out vec4 FragColor;

void main()
{
    vec3 coords = vec3(v_TexCoord, float(u_TargetLayer));
    FragColor = texture(u_LayeredTexture, coords);
}

The layer index is rounded to the nearest integer layer during sampling, and OpenGL clamps it to the range [0, maxLayers - 1]. Filtering modes such as GL_LINEAR interpolate only within the selected 2D slice; they do not filter across adjacent layers.

Unnormalized Direct Fetching with texelFetch()

For post-processing passes, deferred rendering G-buffers, or exact pixel operations where filtering is undesirable, use texelFetch(). This function bypasses filtering and operates on integer coordinates:

ivec2 pixelCoord = ivec2(gl_FragCoord.xy);
int layerIndex = 2;
int lod = 0;

vec4 data = texelFetch(u_LayeredTexture, ivec3(pixelCoord, layerIndex), lod);

The third vector component of the ivec3 directly specifies the integer layer index.

Framebuffer Setup Considerations

Before reading, the texture array must be unlinked from the active write framebuffer or bound to a separate texture unit after rendering to avoid read-after-write hazards. When rendering to layers initially, the target is bound to an FBO via glFramebufferTexture() for layered rendering via geometry shaders or multiview extensions, or via glFramebufferTextureLayer() for per-layer rendering passes. Once drawing commands complete, bind the texture to an active texture unit using glBindTextureUnit() or glBindTexture(GL_TEXTURE_2D_ARRAY, ...), update the sampler uniform, and execute your sampling shader.