Repository navigation
Clustered Lighting
SparkEngine uses GPU-based clustered light culling to efficiently determine which lights affect each region of the screen. The camera frustum is subdivided into a 3D grid of clusters, and each light is assigned to the clusters it overlaps, enabling O(1) light lookups per pixel during shading.
Source: SparkEngine/Source/Graphics/ClusteredLightCulling.h, SparkEngine/Source/Graphics/ClusteredLightGPU.h
Namespace: Spark::Graphics
Tests: Tests/TestClusteredLightGPU.cpp (7 test cases)
With many lights in a scene, testing every light for every pixel is prohibitively expensive. Clustered lighting divides the frustum into a 3D grid and pre-computes which lights affect each cell, so the pixel shader only evaluates a small subset of lights.
┌──────────────────────────────────────────────────────────┐
│ Camera Frustum │
│ │
│ Near ──────────────────────────────────────────── Far │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┐ ← Y slices │
│ │ C0 │ C1 │ C2 │ C3 │ C4 │ C5 │ C6 │ C7 │ │
│ ├────┼────┼────┼────┼────┼────┼────┼────┤ │
│ │ C8 │ C9 │C10 │C11 │C12 │C13 │C14 │C15 │ ← gridY │
│ └────┴────┴────┴────┴────┴────┴────┴────┘ │
│ ↑ ↑ │
│ gridX gridZ (depth) │
│ │
│ Each cluster stores: │
│ - light count │
│ - list of light indices │
│ │
│ Pixel shader: cluster = f(screenXY, depth) │
│ lights = clusterLightList[cluster] │
└──────────────────────────────────────────────────────────┘
struct ClusterConfig
{
uint32_t gridX = 16; // Clusters along screen X
uint32_t gridY = 8; // Clusters along screen Y
uint32_t gridZ = 24; // Clusters along depth
uint32_t maxLightsPerCluster = 200; // Light index list capacity
uint32_t maxTotalLights = 1000; // Maximum active lights
};Default grid: 16 x 8 x 24 = 3,072 clusters.
Each cluster is represented as an axis-aligned bounding box in view space:
struct ClusterAABB
{
XMFLOAT3 minBounds;
XMFLOAT3 maxBounds;
};Cluster AABBs are rebuilt when the projection matrix changes (e.g., FOV or aspect ratio change).
struct LightData
{
XMFLOAT3 position;
float radius;
XMFLOAT3 color;
float intensity;
uint32_t type; // 0 = point, 1 = spot
};Update() performs two steps:
-
Rebuild grid (
RebuildClusterGrid): Compute cluster AABBs from the projection matrix, near/far planes, and grid dimensions. Depth slices use logarithmic distribution for higher near-camera resolution. -
Assign lights (
AssignLightsToClusters): For each light, transform its position to view space and test its bounding sphere against each cluster's AABB. If they intersect, the light's index is added to that cluster's light list.
The sphere-AABB intersection test is conservative — it may include a few extra lights per cluster, but never misses one.
| Method | Description |
|---|---|
GetInstance() |
Singleton access |
Initialize(config) |
Create grid with given dimensions |
Shutdown() |
Release all resources |
AddLight(light) |
Add a light for the next Update |
ClearLights() |
Remove all lights |
Update(viewMatrix, projMatrix, nearZ, farZ) |
Rebuild grid and assign lights |
GetClusterCount() |
Total clusters (gridX * gridY * gridZ) |
GetActiveLightCount() |
Lights in the active list |
GetConfig() |
Current grid configuration |
GetClusterLightCount(index) |
Light count for a specific cluster |
GetClusterLightIndices(index) |
Light index array for a cluster |
Console_GetStatus() |
Formatted debug string |
Upload cluster data for use in pixel shaders:
culling.CreateGPUBuffers(rhiDevice);
// After Update() each frame:
culling.UploadToGPU(rhiDevice);
// Bind in shader
auto* clusterBuf = culling.GetClusterBuffer(); // Per-cluster light counts
auto* lightIdxBuf = culling.GetLightIndexBuffer(); // Flat light index arrayThe pixel shader determines its cluster from screen position and depth, reads the light count, and iterates only over assigned lights.
using namespace Spark::Graphics;
auto& culling = ClusteredLightCulling::GetInstance();
ClusterConfig config;
config.gridX = 16;
config.gridY = 8;
config.gridZ = 32;
config.maxLightsPerCluster = 128;
culling.Initialize(config);
// Each frame:
culling.ClearLights();
// Add scene lights
for (const auto& light : sceneLights)
{
LightData ld;
ld.position = light.GetPosition();
ld.radius = light.GetRadius();
ld.color = light.GetColor();
ld.intensity = light.GetIntensity();
ld.type = light.IsSpot() ? 1 : 0;
culling.AddLight(ld);
}
// Build clusters
XMFLOAT4X4 view, proj;
XMStoreFloat4x4(&view, camera.GetViewMatrix());
XMStoreFloat4x4(&proj, camera.GetProjectionMatrix());
culling.Update(view, proj, camera.GetNearZ(), camera.GetFarZ());
// Upload to GPU
culling.UploadToGPU(rhiDevice);
LOG_INFO("Clustered lighting: {} lights in {} clusters",
culling.GetActiveLightCount(), culling.GetClusterCount());- LightingSystem: Feeds lights into clustered culling each frame
- LightManager: Provides the scene's active light list
- Deferred Lighting Pass: The lighting pass uses cluster data to evaluate only relevant lights per pixel
- Volumetric Fog: The fog scattering pass can reuse the clustered light list. See Volumetric Fog
- Forward Rendering: Clustered lighting also works with forward rendering by reading cluster data in the forward pixel shader
- Rendering and Graphics — Overall lighting pipeline
- Volumetric Fog — Light scattering uses the same light data
- Global Illumination — Indirect lighting complements direct clustered lighting
- GPU-Driven Rendering — GPU culling for geometry (parallel concept)
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