What Is gl_FragDepth in GLSL Fragment Shaders?

In OpenGL Shading Language (GLSL), gl_FragDepth is a built-in output variable that allows fragment shaders to manually set or override the depth value written to the depth buffer for a given fragment. By default, the GPU automatically computes and assigns a fragment's depth using the interpolated z-coordinate from vertex processing. Manually writing to gl_FragDepth gives developers fine-grained control over occlusion and z-testing, enabling advanced rendering techniques such as raymarched signed distance fields, spherical impostors, and custom depth bias effects, though it carries important performance implications regarding early depth optimizations.

Default Depth Computation vs. Manual Overrides

During standard rasterization, the fixed-function pipeline interpolates the clip-space coordinates of a primitive's vertices across the surface of each generated fragment. The resulting normalized device coordinate (NDC) depth is mapped to the viewport's depth range (typically \([0.0, 1.0]\)) and exposed to the fragment shader as the read-only variable gl_FragCoord.z. If the shader does not write to gl_FragDepth, the hardware automatically writes gl_FragCoord.z into the depth buffer when depth testing is enabled.

When a shader explicitly writes a value to gl_FragDepth, it overrides this automatic assignment:

#version 330 core

out vec4 FragColor;

void main()
{
    // Compute custom fragment color
    FragColor = vec4(1.0, 0.5, 0.2, 1.0);

    // Explicitly write a modified depth value
    gl_FragDepth = 0.5; 
}

The assigned value must be a floating-point scalar, generally clamped between \(0.0\) and \(1.0\), corresponding to the near and far clipping planes of the active depth buffer.

Common Use Cases for gl_FragDepth

Manual depth control is essential in several specialized rendering pipelines:

  • Raymarching and Volumetric Rendering: When rendering implicit surfaces, fractals, or volumes via sphere tracing inside a bounding volume (such as a cube or full-screen quad), the actual intersection point lies behind the rasterized geometry. Writing the calculated ray-intersection depth to gl_FragDepth ensures the volume correctly intersects and occludes other scene objects.
  • 3D Impostors and Billboarding: Flat quads facing the camera to represent 3D spheres or particles can compute an analytical sphere normal and depth offset in the fragment shader. Setting gl_FragDepth makes the 2D quad behave as a volumetric sphere during depth tests against adjacent geometry.
  • Custom Shadow Mapping and Depth Biasing: Shaders can modify the depth value written during shadow map generation passes to implement slope-scaled or nonlinear bias adjustments directly in code.
  • Logarithmic Depth Buffers: In planetary or space rendering engines where scenes span astronomical distances, setting gl_FragDepth using a logarithmic formula preserves precision across extreme near-to-far ratios.

Performance Considerations and Early-Z Testing

Modern GPUs optimize rendering throughput using Early Depth Testing (Early-Z), where depth and stencil tests occur before the fragment shader executes. If a fragment is determined to be occluded by previously rendered geometry, the GPU discards it immediately, avoiding the computational cost of running complex lighting, texturing, or math operations.

Writing to gl_FragDepth disrupts this optimization. Because the final depth value depends on calculations inside the shader, the GPU cannot determine fragment visibility beforehand and must disable standard Early-Z testing. This forces the fragment shader to execute for all rasterized fragments, increasing overdraw costs and potentially degrading framerates.

Preserving Performance with Conservative Depth

To mitigate the performance impact of disabling Early-Z, GLSL (version 4.20 and newer, or via the GL_ARB_conservative_depth extension) provides depth layout qualifiers. These qualifiers inform the GPU about how gl_FragDepth will be modified relative to gl_FragCoord.z, allowing the driver to preserve partial Early-Z culling:

#version 430 core

// Inform the GPU that the custom depth is always greater than or equal to gl_FragCoord.z
layout (depth_greater) out float gl_FragDepth;

out vec4 FragColor;

void main()
{
    FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    gl_FragDepth = gl_FragCoord.z + 0.05;
}

Available layout qualifiers include:

  • depth_any: The default behavior. Depth can change in any direction, disabling Early-Z optimizations.
  • depth_greater: The shader will only write values greater than or equal to gl_FragCoord.z.
  • depth_less: The shader will only write values less than or equal to gl_FragCoord.z.
  • depth_unchanged: The shader may write to gl_FragDepth, but the written value will always equal gl_FragCoord.z.

Using these qualifiers allows the GPU hardware to perform conservative tests against the depth buffer before shader invocation, recovering much of the performance lost when manual depth writes are required.