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_FragDepthensures 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_FragDepthmakes 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_FragDepthusing 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 togl_FragCoord.z.depth_less: The shader will only write values less than or equal togl_FragCoord.z.depth_unchanged: The shader may write togl_FragDepth, but the written value will always equalgl_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.