Repository navigation
Global Illumination
SparkEngine provides two complementary probe-based global illumination systems: DDGI (Dynamic Diffuse Global Illumination) for regular-grid irradiance probes, and Adaptive Probe Volumes (APV) for hierarchical brick-based probes with geometry-aware subdivision. Both use L2 spherical harmonics for compact irradiance storage.
Source: SparkEngine/Source/Graphics/DDGIProbeSystem.h, SparkEngine/Source/Graphics/AdaptiveProbeVolumes.h
Namespace: Spark::Graphics
- Overview
- DDGI Probe System
- Adaptive Probe Volumes
- DDGI vs APV
- GPU Resources
- Usage Examples
- Integration
- See Also
Both systems compute indirect diffuse lighting by placing probe grids in the scene, capturing irradiance from ray-cast samples, and interpolating the results at shading time.
┌─────────────────────────────────────────────────────────────┐
│ Scene Geometry │
│ │
│ ● ─── ● ─── ● ─── ● DDGI: Regular 3D grid │
│ | | | | of probes at fixed spacing │
│ ● ─── ● ─── ● ─── ● │
│ | | | | │
│ ● ─── ● ─── ● ─── ● │
│ │
│ ┌────┐ APV: Brick-based hierarchy │
│ │●●●●│ ┌──┐ Fine bricks near geometry, │
│ │●●●●│ │●●│ coarse bricks in open areas │
│ │●●●●│ │●●│ │
│ │●●●●│ └──┘ │
│ └────┘ │
│ │
│ Each probe stores L2 SH (9 coefficients x 3 RGB channels)│
│ = 27 floats per probe │
└─────────────────────────────────────────────────────────────┘
| Constant | DDGI | APV |
|---|---|---|
| SH coefficients per channel | 9 (L2) | 9 (L2) |
| Floats per probe | 27 | 27 |
| Probes per brick | N/A | 64 (4x4x4) |
A regular 3D grid of irradiance probes with real-time updates via ray casting.
struct DDGISettings
{
float spacingX = 2.0f, spacingY = 2.0f, spacingZ = 2.0f;
uint32_t countX = 8, countY = 4, countZ = 8;
float originX = 0.0f, originY = 0.0f, originZ = 0.0f;
int raysPerProbe = 128;
float hysteresis = 0.97f;
float maxRayDistance = 50.0f;
float normalBias = 0.25f;
float relocationThreshold = 0.5f;
float relocationMaxOffset = 1.0f;
};| Setting | Default | Description |
|---|---|---|
spacing |
2m | Distance between probes on each axis |
count |
8x4x8 = 256 | Number of probes per axis |
raysPerProbe |
128 | Rays cast per probe per update cycle |
hysteresis |
0.97 | Temporal blend factor (higher = smoother, slower) |
maxRayDistance |
50m | Ray trace distance limit |
normalBias |
0.25m | Bias along surface normal to prevent self-shadowing |
Each probe stores irradiance as L2 spherical harmonics — 9 basis functions per color channel:
struct DDGIProbe
{
std::array<float, 9> r{}; // 9 SH coefficients for red
std::array<float, 9> g{}; // 9 SH coefficients for green
std::array<float, 9> b{}; // 9 SH coefficients for blue
float offsetX, offsetY, offsetZ; // Relocation offset
bool relocated;
};SH basis functions are evaluated using EvaluateSHBasis() with a normalized direction vector, producing 9 basis values that are dot-multiplied with the stored coefficients to reconstruct irradiance.
// For each probe in the grid:
DDGIRayResult rays[128];
// ... cast rays using cosine-weighted hemisphere sampling ...
system.UpdateProbe(ix, iy, iz, rays, 128);
system.UpdateProbeBorders();UpdateProbe() performs:
- SH projection: Each ray's radiance is projected into SH basis functions using spherical Fibonacci sampling for quasi-uniform direction distribution
-
Hysteresis blending: New SH coefficients are blended with existing values:
new = lerp(current, incoming, 1 - hysteresis) -
Border copy:
UpdateProbeBorders()copies edge texels for seamless hardware bilinear filtering in the GPU texture atlas
Probes inside or very close to geometry produce biased irradiance. Relocation nudges them along the surface normal:
system.RelocateProbe(ix, iy, iz,
closestHitDistance, hitNormalX, hitNormalY, hitNormalZ);Relocation is limited to relocationMaxOffset (default 1m) to keep probes near their grid position.
DDGIIrradiance result = system.QueryIrradiance(
worldX, worldY, worldZ,
normalX, normalY, normalZ);Performs trilinear interpolation of the 8 nearest probes. Each probe's SH is evaluated at the given surface normal before interpolation.
| Method | Description |
|---|---|
Initialize(settings) |
Create probe grid |
Shutdown() |
Release probe data |
GetProbePosition(ix, iy, iz) |
World position of a grid probe |
UpdateProbe(ix, iy, iz, rays, count) |
Update one probe with ray results |
UpdateProbeBorders() |
Copy border texels for filtering |
RelocateProbe(ix, iy, iz, ...) |
Push probe away from geometry |
QueryIrradiance(pos, normal) |
Trilinear SH interpolation |
GetProbeCount() |
Total probes (countX * countY * countZ) |
CreateGPUResources(device) |
Create structured buffer for SH data |
UploadProbeDataToGPU(device) |
Upload SH to GPU |
A brick-based hierarchical system that adapts probe density to scene geometry, inspired by Unity's Adaptive Probe Volumes.
Probes are organized in bricks of 4x4x4 = 64 probes each. Bricks are sparse — only regions of interest are populated.
struct APVBrick
{
BrickID id;
std::array<APVProbeData, 64> probes; // 4x4x4
float originX, originY, originZ; // World-space corner
float spacing; // Probe spacing within brick
bool loaded;
bool subdivided;
};| LOD | Spacing | Use Case |
|---|---|---|
Fine (0) |
1m | Near geometry surfaces |
Medium (1) |
2m | Transitional areas |
Coarse (2) |
4m | Open areas, far from camera |
Coarse bricks near geometry can be subdivided into 8 finer bricks:
system.SubdivideBrick(gridX, gridY, gridZ, BrickLOD::Coarse);
// Creates 8 Medium bricks; their probes are interpolated from the parentThe parent brick is marked as subdivided and its data is preserved for fallback. Child bricks inherit initial SH values via interpolation.
Like DDGI relocation, but per-probe within each brick:
system.ApplyVirtualOffsets(brickId, geometryDistances, geometryNormals);Probes too close to (or inside) geometry are pushed away along the surface normal by up to virtualOffsetDistance (default 0.25m).
system.UpdateStreaming(cameraPosX, cameraPosY, cameraPosZ);Loads bricks within streamingDistance of the camera and unloads distant ones, keeping memory usage bounded.
| Method | Description |
|---|---|
Initialize(settings) |
Set up with APVSettings
|
Shutdown() |
Release all brick data |
CreateBrick(x, y, z, lod) |
Create a brick at grid position |
SubdivideBrick(x, y, z, parentLOD) |
Split into 8 finer bricks |
ApplyVirtualOffsets(id, distances, normals) |
Push probes from surfaces |
UpdateStreaming(camPos) |
Load/unload bricks by distance |
SetProbeData(id, px, py, pz, shR, shG, shB) |
Store baked SH data |
SampleIrradiance(pos, normal) |
Query irradiance at world position |
GetLoadedBrickCount() |
Active bricks in memory |
GetTotalProbeCount() |
brickCount * 64 |
CreateGPUResources(device) |
Create structured buffer for bricks |
UploadBrickDataToGPU(device) |
Upload active bricks to GPU |
| Feature | DDGI | Adaptive Probe Volumes |
|---|---|---|
| Grid type | Regular 3D | Sparse brick hierarchy |
| Probe density | Uniform everywhere | Adaptive (fine near geometry) |
| Memory | Fixed (countX * countY * countZ) | Dynamic (loaded bricks only) |
| Streaming | No | Yes (camera distance) |
| Subdivision | No | Yes (3 LOD levels) |
| Real-time update | Yes (per-probe ray cast) | Typically baked, can update |
| Best for | Small-medium dynamic scenes | Large worlds with baked lighting |
| Virtual offsets | Probe relocation | Per-probe virtual offsets |
Use DDGI for fully dynamic lighting in contained areas (e.g., interiors, arenas). Use APV for large open worlds where baked GI with streaming is needed.
Both systems create GPU structured buffers for shader access:
// DDGI
ddgi.CreateGPUResources(device);
ddgi.UploadProbeDataToGPU(device);
auto* probeBuffer = ddgi.GetProbeBuffer(); // SH data structured buffer
// APV
apv.CreateGPUResources(device);
apv.UploadBrickDataToGPU(device);
auto* brickBuffer = apv.GetBrickBuffer(); // Brick SH dataDDGIProbeSystem ddgi;
DDGISettings settings;
settings.countX = 12; settings.countY = 6; settings.countZ = 12;
settings.spacingX = 3.0f; settings.spacingY = 2.0f; settings.spacingZ = 3.0f;
settings.raysPerProbe = 64;
ddgi.Initialize(settings);
// Update loop: update a subset of probes each frame
for (int i = 0; i < probesPerFrame; ++i)
{
auto [ix, iy, iz] = GetNextProbeToUpdate();
DDGIRayResult rays[64];
CastProbeRays(ddgi, ix, iy, iz, rays, 64);
ddgi.UpdateProbe(ix, iy, iz, rays, 64);
}
ddgi.UpdateProbeBorders();
// Query in shader setup
DDGIIrradiance irr = ddgi.QueryIrradiance(
fragWorldPos.x, fragWorldPos.y, fragWorldPos.z,
fragNormal.x, fragNormal.y, fragNormal.z);AdaptiveProbeVolumes apv;
APVSettings settings;
settings.streamingDistance = 80.0f;
settings.subdivisionGeometryThreshold = 1.5f;
apv.Initialize(settings);
// Create coarse bricks covering the scene
for (int z = -5; z <= 5; ++z)
for (int y = 0; y <= 2; ++y)
for (int x = -5; x <= 5; ++x)
apv.CreateBrick(x, y, z, BrickLOD::Coarse);
// Subdivide near a building
apv.SubdivideBrick(0, 0, 0, BrickLOD::Coarse);
// Each frame
apv.UpdateStreaming(cameraPos.x, cameraPos.y, cameraPos.z);
// Query
APVIrradiance irr = apv.SampleIrradiance(
worldX, worldY, worldZ, normalX, normalY, normalZ);- LightingSystem: GI results are combined with direct lighting in the deferred lighting pass
- Render Graph: DDGI probe updates can be scheduled as compute passes
- Large World Support: APV streaming pairs well with Large World Support for seamless area transitions
- Day-Night Cycle: DDGI probes update dynamically with changing light conditions
- Rendering and Graphics — Overall lighting architecture
- Volumetric Fog — Volumetric scattering that complements GI
- Clustered Lighting — Direct light culling system
- Large World Support — World streaming for APV integration
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers