This project is an implementation of Virtual Shadow Maps based on the GPU Zen 3 paper "Virtual Shadow Maps" by Matej Sakmary, Jake Ryan, Justin Hall, and Alessio Lustri.
Virtual Shadow Maps is a shadow mapping technique that uses virtual texturing to efficiently handle very high-resolution shadow maps with minimal memory overhead. It addresses both perspective aliasing and projective aliasing issues common in traditional shadow mapping.
- Virtual Page Table (VPT): Maps virtual shadow map pages to physical memory
- Physical Page Table (PPT): Inverse mapping from physical to virtual pages
- 16 Cascades: 4096ร4096 resolution per cascade, organized concentrically
- Page-based Allocation: 128ร128 texel pages, allocated on-demand
- Page Caching: Persistent pages across frames with sliding window addressing
- Free Invalidated Pages: Clears pages affected by light movement or dynamic objects
- Mark Visible Pages: Analyzes camera depth buffer to determine visible shadow pages
- Page Allocation: Assigns physical memory to visible virtual pages
- Clear Pages: Prepares newly allocated pages for rendering
- Hierarchical Page Buffer (HPB): Multi-level culling structure for efficient geometry processing
- Depth Rendering: Renders shadow casters to allocated pages
- Atomic Min Operations: Thread-safe depth writes to physical memory
- Cascade Selection: Distance-based heuristic for optimal shadow detail
- Virtual to Physical Mapping: Translates sample coordinates through page tables
- PCF Filtering: Soft shadow support with percentage-closer filtering
VSMUnityProject/
โโโ Assets/
โ โโโ Scripts/
โ โ โโโ VSM/
โ โ โ โโโ VSMConstants.cs # Core constants and bit packing
โ โ โ โโโ VSMPageTable.cs # Virtual page table management
โ โ โ โโโ VSMPhysicalPageTable.cs # Physical page table (in VSMPageTable.cs)
โ โ โ โโโ VSMPhysicalMemory.cs # Physical shadow memory texture
โ โ โ โโโ VSMHierarchicalPageBuffer.cs # HPB for culling
โ โ โ โโโ VirtualShadowMapManager.cs # Main orchestration component
โ โ โโโ Utils/
โ โ โโโ SimpleController.cs # Camera controller for testing
โ โโโ Shaders/
โ โโโ Include/
โ โ โโโ VSMCommon.hlsl # Shared shader utilities
โ โโโ VSM/
โ โโโ VSMFreeInvalidatedPages.compute # Bookkeeping: free pages
โ โโโ VSMMarkVisiblePages.compute # Bookkeeping: mark visible
โ โโโ VSMAllocatePages.compute # Bookkeeping: allocation
โ โโโ VSMClearPages.compute # Bookkeeping: clear
โ โโโ VSMBuildHPB.compute # Build hierarchical culling buffer
โ โโโ VSMDepthRender.shader # Depth rendering to pages
โ โโโ VSMSampling.hlsl # Shadow sampling functions
โ โโโ VSMShadowReceiver.shader # Example lit shader with shadows
Virtual Space:
- 16 cascades ร 4096ร4096 pixels = ~268 million virtual texels
- Each cascade is 2ร the size of the previous (first cascade: 2m, last: 65km)
Physical Memory:
- 2048 pages maximum (64ร32 grid)
- 8192ร4096 physical texture (R32_Float)
- Only visible pages consume memory (~128KB per page)
Page Entry Format (32-bit):
- Bit 0: Allocated flag
- Bit 1: Visible flag
- Bit 2: Dirty flag
- Bits 3-13: Physical page X coordinate (11 bits)
- Bits 14-24: Physical page Y coordinate (11 bits)
Distance-based (Implemented):
level = max(ceil(log2(distance / firstCascadeSize)), 0)
Screen-space (Alternative):
level = max(ceil(log2(texelWorldSize / cascade0TexelSize)), 0)
To maintain cache coherency when the camera moves:
- Cascade frustums snap to page grid
- Light position slides parallel to near-plane
- Virtual coordinates wrap around using modulo arithmetic
- Cached pages remain valid across frames
- Unity 2021.3 or later recommended
- Requires compute shader support (Shader Model 5.0+)
-
Import Project: Copy the
VSMUnityProject
folder into your Unity projects directory -
Create Scene:
- Create a new scene
- Add a Camera with
VirtualShadowMapManager
component - Add a Directional Light
- Create ground plane and test objects
-
Configure Manager:
- Assign Directional Light reference
- Assign compute shaders to respective slots
- Set first cascade size (default: 2.0m)
- Configure shadow caster layer mask
-
Apply Materials:
- Use
VSM/ShadowReceiver
shader on objects receiving shadows - Configure shadow strength and appearance
- Use
-
Assign Shaders:
- Assign all compute shaders from
Assets/Shaders/VSM/
to the manager - Assign
VSMDepthRender.shader
for depth rendering
- Assign all compute shaders from
- Page Caching: Reduces per-frame rendering by reusing stable pages
- HPB Culling: Skips geometry that doesn't intersect dirty pages
- On-demand Allocation: Only visible pages consume memory
- Atomic Operations: Lock-free depth writes for parallel rendering
- Cascade count can be reduced for lower-end hardware
- Page size can be adjusted (64ร64 or 256ร256)
- Physical memory pool can be tuned based on VRAM budget
- Simplified Allocation: The allocation system uses a simplified hash-based approach rather than proper buffer consumption
- No Mesh Shader Support: The paper uses mesh shaders for culling; this implementation uses compute shaders
- Limited Dynamic Object Tracking: Dynamic object invalidation masks are stubbed
- No PCSS: Only basic PCF filtering is implemented
- Proper physical page allocation with consume buffers
- Mesh shader-based rendering path
- Dynamic object tracking and invalidation
- PCSS (Percentage-Closer Soft Shadows)
- Multi-light support
- Debug visualization modes (cascade levels, page states)
- Performance profiling and optimization passes
- GPU Zen 3: "Virtual Shadow Maps" - Matej Sakmary, Jake Ryan, Justin Hall, Alessio Lustri
- Unreal Engine 5 Documentation - Virtual Shadow Maps
- "Cascaded Shadow Maps" - Wolfgang Engel, 2007
- "Sample Distribution Shadow Maps" - Andrew Lauritzen et al., 2011
- "Virtual Texturing" - Sean Barrett, 2008
This implementation is provided for educational purposes. Please refer to the original paper for detailed algorithmic descriptions.
For issues or questions about this implementation, please refer to the GPU Zen 3 book or Unreal Engine 5 documentation for algorithmic details.