Logarithmic Depth Buffering#

The Project Reality Team implemented logarithmic depth buffering in their 1.7.3 update. This document outlines a simplified implementation of logarithmic depth buffering in HLSL, based on Outerra’s optimized GLSL approach from 2013.

Logarithmic Depth Buffering Pixel Shader#
float4x4 _WorldViewProj : WorldViewProj;

struct APP2VS
{
   float4 Pos : POSITION0;
};

struct VS2PS
{
   float4 HPos : POSITION;
   float Depth : TEXCOORD0;
};

struct PS2FB
{
   float4 Color : COLOR;
   float Depth : DEPTH;
};

// Converts linear depth to logarithmic depth in the pixel shader.
// Source: https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html
float ApplyLogarithmicDepth(float Depth)
{
   const float FarPlane = 10000.0;
   const float FCoef = 1.0 / log2(FarPlane + 1.0);
   return log2(Depth) * FCoef;
}

VS2PS VS_LogDepth(APP2VS Input)
{
   VS2PS Output = (VS2PS)0;

   // Apply world-view-projection transformation.
   Output.HPos = mul(Input.Pos, _WorldViewProj);

   // Output depth (w-component of homogeneous position + 1).
   Output.Depth = Output.HPos.w + 1.0;

   return Output;
}

PS2FB PS_LogDepth(VS2PS Input)
{
   PS2FB Output;

   // Output a color value (required).
   Output.Color = 0.0;

   // Output logarithmic depth for per-fragment depth testing.
   Output.Depth = ApplyLogarithmicDepth(Input.Depth);

   return Output;
}

Correcting Outerra’s Logarithmic Depth Buffering#

Outerra provides a vertex shader implementation of logarithmic depth buffering in GLSL (outerra.blogspot.com). However, their implementation omits a crucial step. This document presents a corrected version of Outerra’s logarithmic depth buffering in HLSL.

Outerra’s implementation lacks a final multiplication by the homogeneous coordinate ‘W’. This multiplication is essential because the GPU automatically divides the vertex position HPos by W during the perspective divide.

// Output.HPos is the computed vertex position in homogeneous space.
const float FarPlane = 10000.0;
const float FCoef = 1.0 / log2(FarPlane + 1.0);
Output.HPos.z = saturate(log2(max(1e-6, Output.HPos.w)) * FCoef);
Output.HPos.z *= Output.HPos.w;