How to Use SV_Depth in DirectX Pixel Shaders
This article provides an overview of how the SV_Depth
system-value semantic operates within DirectX High-Level Shader Language
(HLSL) to override hardware-calculated depth values. By binding this
semantic to a pixel shader output, developers can programmatically alter
the depth buffer on a per-pixel basis, allowing for custom occlusion,
volumetric effects, and geometric depth manipulation beyond standard
rasterizer interpolation.
The Role of SV_Depth in HLSL
In a standard DirectX rendering pipeline, depth is calculated automatically during rasterization. The vertex shader outputs the position in clip space, and the hardware interpolates the \(z\) and \(w\) coordinates across the primitive to compute a depth value (\(z/w\)) for each pixel. This default value is then tested against and written to the depth-stencil buffer.
The SV_Depth semantic allows a pixel shader to intervene
in this process by explicitly writing a custom floating-point value to
the depth buffer instead of using the hardware-generated value.
Implementation in the Pixel Shader
To use SV_Depth, you define it as an output attribute
within the pixel shader's return structure or as an out
parameter.
struct PixelOutput
{
float4 Color : SV_Target;
float Depth : SV_Depth;
};
PixelOutput main(PixelInput input)
{
PixelOutput output;
output.Color = float4(1.0, 0.0, 0.0, 1.0);
// Explicitly modify the depth value (must be in the range [0.0, 1.0])
output.Depth = input.CustomDepth;
return output;
}
The value written to SV_Depth must fall within the
normalized viewport depth range, typically \([0.0, 1.0]\) in DirectX (representing the
near and far clipping planes). Any value written outside this range is
clamped or can cause clipping artifacts depending on the depth-stencil
state configuration.
Performance Impact: Early-Z Disabling
Under normal conditions, modern GPUs utilize an optimization known as Early-Z, running the depth test before the pixel shader executes. If a pixel is occluded by previously rendered geometry, the GPU discards it immediately, saving the computational cost of running the pixel shader.
When a pixel shader outputs to SV_Depth, the hardware
can no longer predict the final depth value prior to execution. As a
result:
- Early-Z is disabled: The GPU must execute the pixel shader for every rasterized fragment to discover its final depth.
- Fill rate decreases: Scenes with high overdraw will experience a performance penalty because occluded pixels are fully shaded before depth rejection can occur.
Preserving Optimizations with Conservative Depth
To mitigate the performance loss associated with disabling Early-Z, DirectX 11 and later introduced conservative depth semantics. These semantics allow the developer to guarantee directional bounds on the modified depth, allowing the GPU to keep certain Early-Z optimizations active:
SV_DepthGreaterEqual: Asserts that the shader will only output a depth value greater than or equal to the hardware-generated rasterizer depth. If the original depth already fails the depth test, the pixel can be discarded early.SV_DepthLessEqual: Asserts that the shader will only output a depth value less than or equal to the hardware-generated rasterizer depth.
Practical Use Cases
Explicit depth modification via SV_Depth is commonly
employed in advanced graphics techniques:
- Raymarching and Signed Distance Fields (SDFs): When
rendering procedural surfaces inside a bounding proxy box,
SV_Depthis used to write the exact intersection point calculated by the raymarcher into the depth buffer, ensuring correct interaction with standard rasterized objects. - Impostors and Billboards: Volumetric particles or spherical impostors can write reconstructed analytical depths so they clip and blend realistically against surrounding geometry.
- Decals and Intersecting Geometry: Shaders can smooth out seams or adjust depth discrepancies dynamically to eliminate Z-fighting.