Forward Decal Rendering with Depth Buffers in DirectX
Forward decal rendering is a technique used in computer graphics to project dynamic 2D textures—such as bullet impacts, footprints, or stains—onto complex, existing 3D geometry without altering the original meshes. This article explains the fundamentals of forward decal rendering, examines how it leverages depth buffers to project textures accurately, and outlines its implementation pipeline within DirectX.
What is Forward Decal Rendering?
In real-time rendering, decals add localized surface details to scenes. In a forward rendering pipeline, where lighting and shading are computed directly as geometry is drawn, decals cannot simply read from a deferred G-buffer. Instead, forward decal rendering typically uses an untextured geometric volume—most commonly an oriented bounding box (OBB)—representing the projection bounds of the decal.
When this volume is rendered, a pixel shader determines which parts of the underlying scene geometry intersect the volume and maps the decal texture across those surfaces.
The Role of the Depth Buffer
The depth buffer (or Z-buffer) stores the normalized device coordinate (NDC) depth values of the geometry closest to the camera for every pixel on the screen. Forward decal rendering relies on this depth information to accurately map decals onto static or dynamic geometry without creating dedicated duplicate geometry.
Because forward rendering computes lighting immediately, using depth-projected decals requires an existing depth buffer. Consequently, the rendering pipeline must include a Depth Pre-Pass. During this pass, opaque scene geometry is rendered to populate the Depth-Stencil View (DSV) before the decal pass executes.
How Forward Decals Utilize Depth in DirectX
The implementation of forward decals in DirectX (Direct3D 11 or 12) follows a structured sequence within the vertex and pixel shaders:
1. Rendering the Decal Bounding Volume
The CPU submits draw calls for the decal boxes. The DirectX pipeline
disables depth writes
(DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO) to prevent
decals from occluding subsequent geometry, while setting the depth test
to allow the volume to be processed where it intersects or encloses the
scene. Face culling is typically inverted (culling front faces instead
of back faces) so the volume is still drawn even if the camera moves
inside the decal box.
2. Screen-Space Coordinate Reconstruction
In the HLSL pixel shader, the system-generated value
SV_Position provides the integer screen coordinates \((x, y)\) of the current pixel. These
coordinates are used to calculate the normalized screen UV
coordinates:
float2 screenUV = input.positionCS.xy / screenSize.xy;
Using these UVs, the shader samples the depth texture
(Texture2D<float> DepthTexture) bound as a Shader
Resource View (SRV):
float depth = DepthTexture.Sample(PointClampSampler, screenUV).r;
3. World and Object Position Reconstruction
The pixel shader uses the sampled depth value along with the screen UVs to construct a homogeneous clip-space coordinate:
\[\mathbf{P}_{\text{clip}} = \left( \text{screenUV}.x \times 2 - 1, (1 - \text{screenUV}.y) \times 2 - 1, \text{depth}, 1.0 \right)\]
This point is multiplied by the inverse View-Projection matrix to obtain the world-space position:
\[\mathbf{P}_{\text{world}} = \mathbf{P}_{\text{clip}} \times \mathbf{M}_{\text{invViewProj}}\] \[\mathbf{P}_{\text{world}} = \frac{\mathbf{P}_{\text{world}}.xyz}{\mathbf{P}_{\text{world}}.w}\]
To determine if the underlying surface falls within the decal’s bounds, the world position is transformed into the decal box's local object space using the inverse world matrix:
\[\mathbf{P}_{\text{local}} = \mathbf{P}_{\text{world}} \times \mathbf{M}_{\text{invWorld}}\]
4. Clipping and Texture Sampling
Assuming a unit cube centered at the origin, the local coordinates
span from \([-0.5, -0.5, -0.5]\) to
\([0.5, 0.5, 0.5]\). If \(\mathbf{P}_{\text{local}}\) exceeds these
bounds along any axis, the pixel shader discards the pixel using the
HLSL clip() intrinsic:
clip(0.5f - abs(localPos.xyz));
For remaining pixels, the local \(X\) and \(Z\) (or \(X\) and \(Y\), depending on the projection orientation) coordinates are remapped from \([-0.5, 0.5]\) to \([0.0, 1.0]\) to form UV coordinates for sampling the decal texture.
5. Output and Blending
The sampled decal color is shaded and output to the render target.
Standard hardware alpha blending (D3D12_BLEND_SRC_ALPHA and
D3D12_BLEND_INV_SRC_ALPHA) merges the decal with the
existing scene color.
Key Considerations
- Normals and Angle Fading: Depth alone only provides
position. To avoid texture stretching across perpendicular surfaces,
shaders often sample a pre-pass normal buffer or use screen-space
derivatives (
ddx,ddy) on the reconstructed positions to calculate the surface normal and fade out decals projected at steep angles. - Overhead: Reconstructing positions and sampling the depth buffer per pixel introduces a computational cost. Tightly fitting the decal bounding volume minimizes unnecessary pixel shader invocations and reduces overdraw.