Repository navigation
DXR Raytracing
SparkEngine supports hardware-accelerated ray tracing via DirectX Raytracing (DXR) 1.1. Features include ray-traced reflections, soft shadows, ambient occlusion, and global illumination.
-
Namespace:
Spark::Graphics -
Files:
Graphics/RHI/DXRSupport.h/.cpp - Dependencies: D3D12 backend, DXR-capable GPU
Singleton manager that handles the full DXR pipeline:
auto& dxr = DXRManager::GetInstance();
dxr.Initialize(d3d12Device); // Queries DXR tier
// Build acceleration structures
uint32_t blas = dxr.CreateBLAS(blasDesc);
dxr.BuildTLAS(instances);
// Dispatch ray tracing
dxr.TraceReflections(viewProj, cameraPos);
dxr.TraceShadows(lightDir);
dxr.TraceAmbientOcclusion(viewProj, cameraPos);
dxr.TraceGlobalIllumination(viewProj, cameraPos);enum class RTFeature : uint32_t {
Reflections = 1 << 0,
Shadows = 1 << 1,
AmbientOcclusion = 1 << 2,
GlobalIllumination = 1 << 3,
All = 0xFFFFFFFF
};- BLAS (Bottom-Level): Per-mesh geometry, supports update/refit for animation
- TLAS (Top-Level): Scene-wide instance list with transforms and hit group indices
- Memory tracked via
DXRStats::accelerationStructureMemory
| Preset | Reflections SPP | Bounces | Shadows SPP | Render Scale |
|---|---|---|---|---|
| Low | 1 | 1 | 1 | 0.5x |
| Medium | 1 | 1 | 2 | 0.75x |
| High | 2 | 2 | 4 | 1.0x |
| Ultra | 4 | 3 | 8 | 1.0x |
The implementation includes HLSL ray tracing shaders:
- Reflection RayGen: Traces reflection rays from G-Buffer normals
- Shadow RayGen: Soft shadow rays with jittered sampling
- AO RayGen: Cosine-weighted hemisphere sampling
- GI RayGen: Multi-bounce diffuse global illumination
Timestamp queries measure per-feature timing:
auto stats = dxr.GetStats();
stats.rtReflectionsTimeMs;
stats.rtShadowsTimeMs;
stats.rtAOTimeMs;
stats.rtGITimeMs;Bottom-Level Acceleration Structures contain the actual triangle geometry for a single mesh. The build process is:
-
Geometry Description: Each BLAS is described by one or more
D3D12_RAYTRACING_GEOMETRY_DESCentries. SparkEngine sets the vertex buffer, index buffer, vertex stride, vertex count, index count, and transform for each sub-mesh. -
Prebuild Info Query:
GetRaytracingAccelerationStructurePrebuildInfo()returns the required scratch and result buffer sizes. -
Buffer Allocation: The
DXRManagerallocates GPU buffers for the scratch space (temporary) and the result (persistent BLAS data) from a dedicated acceleration structure memory pool. -
Build Command:
BuildRaytracingAccelerationStructure()is recorded on the command list withD3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACEfor static geometry orPREFER_FAST_BUILDfor dynamic geometry. - UAV Barrier: A UAV barrier is issued after the build to ensure the BLAS is ready before TLAS construction.
BLASDesc desc;
desc.vertexBuffer = mesh->GetVertexBufferGPUAddress();
desc.indexBuffer = mesh->GetIndexBufferGPUAddress();
desc.vertexStride = sizeof(Vertex);
desc.vertexCount = mesh->GetVertexCount();
desc.indexCount = mesh->GetIndexCount();
desc.isOpaque = true;
desc.allowUpdate = mesh->IsAnimated(); // Enable refit for skeletal meshes
uint32_t blasHandle = dxr.CreateBLAS(desc);The Top-Level Acceleration Structure references all BLAS instances in the scene with per-instance transforms, hit group indices, and instance masks.
-
Instance Buffer: An array of
D3D12_RAYTRACING_INSTANCE_DESCis filled, one per visible mesh instance. Each entry contains the 3x4 world transform, instance ID, instance mask (for selective ray tracing), and BLAS address. -
Build: The TLAS is rebuilt every frame to account for moving objects. The
ALLOW_UPDATEflag enables incremental refit when only transforms change. - Performance: TLAS build is typically under 0.5ms for scenes with fewer than 10,000 instances. SparkEngine uses GPU-driven instance culling to reduce the TLAS instance count.
std::vector<TLASInstance> instances;
for (auto [entity, transform, meshRT] : registry.view<Transform, MeshRTComponent>().each())
{
TLASInstance inst;
inst.blasHandle = meshRT.blasHandle;
inst.transform = transform.GetWorldMatrix3x4();
inst.instanceMask = meshRT.rayMask;
inst.hitGroupIndex = meshRT.hitGroupIndex;
instances.push_back(inst);
}
dxr.BuildTLAS(instances);For animated meshes (skeletal animation, vertex deformation), rebuilding the BLAS every frame is expensive. SparkEngine uses a two-tier strategy:
-
Refit: For meshes where the topology does not change (skeletal animation, blend shapes), the BLAS is updated in-place using the
PERFORM_UPDATEflag. This recomputes bounding boxes without rebuilding the BVH tree. Refitting is 5-10x faster than a full rebuild but can degrade traversal performance over time. - Full Rebuild: When refit quality degrades beyond a threshold (measured by tracking traversal step count per ray), or after a configurable number of refit frames (default: 60), a full BLAS rebuild is triggered.
// Automatic refit/rebuild management
dxr.UpdateBLAS(blasHandle, updatedVertexBuffer);
// Internally tracks refit count and triggers rebuild when neededThe DXR shader table organises shader records into three sections:
| Section | Contents | Record Size |
|---|---|---|
| Ray Generation | One record per ray type (reflections, shadows, AO, GI) | 64 bytes (shader ID + root constants) |
| Miss | One record per ray type (return background/zero) | 32 bytes (shader ID only) |
| Hit Group | One record per material/geometry combination | 96 bytes (shader ID + material CBV + texture SRVs) |
The hit group table is the largest and is indexed by: instanceContributionToHitGroupIndex + (rayType * geometryCount) + geometryIndex. SparkEngine pre-computes this mapping when materials are assigned.
// Shader table construction
ShaderTableBuilder builder;
builder.AddRayGenRecord("ReflectionRayGen", reflectionConstants);
builder.AddRayGenRecord("ShadowRayGen", shadowConstants);
builder.AddMissRecord("ReflectionMiss");
builder.AddMissRecord("ShadowMiss");
for (auto& material : materials)
{
builder.AddHitGroupRecord("DefaultHitGroup",
material.constantBufferView,
material.albedoSRV,
material.normalSRV);
}
auto shaderTable = builder.Build(device);- ReflectionRayGen: Reads the G-buffer normal and roughness. For each pixel with roughness below a threshold, traces a reflection ray along the mirror direction with importance-sampled roughness perturbation. Multiple samples per pixel (SPP) are averaged for smoother results at higher quality presets.
-
ShadowRayGen: For each light source, traces a ray toward the light with jittered offsets for soft shadows. Uses
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADERfor maximum performance since only occlusion is needed. - AORayGen: Traces short-range rays in a cosine-weighted hemisphere above the surface normal. The AO value is the ratio of unoccluded rays. Uses a blue noise texture to distribute ray directions across frames for temporal stability.
- GIRayGen: Traces multi-bounce diffuse rays. First bounce uses the G-buffer, subsequent bounces use simplified material evaluation. Irradiance is accumulated in a half-resolution probe grid and filtered spatially.
- ReflectionMiss: Returns the sky/environment map colour sampled along the ray direction.
- ShadowMiss: Returns 1.0 (fully lit) since no occluder was found.
- AOMiss: Returns 1.0 (unoccluded).
- GIMiss: Returns the sky irradiance for the ray direction.
- DefaultClosestHit: Evaluates the material at the hit point (albedo, normal, roughness, metallic) using bindless texture access. For GI bounces, traces secondary rays recursively up to the configured bounce limit.
-
AlphaMaskedClosestHit: Same as default but performs alpha testing. If the alpha value is below the threshold, the ray is continued via
IgnoreHit()(effectively acting as an any-hit rejection).
-
AlphaMaskedAnyHit: Used for foliage and fences. Samples the alpha texture at the hit point and calls
IgnoreHit()if below threshold. This allows rays to pass through transparent portions of alpha-tested geometry.
Raw ray-traced output at low SPP is noisy. SparkEngine applies a multi-pass denoising pipeline:
- Temporal Accumulation: Reproject the previous frame's denoised result using motion vectors. Blend with the current noisy result using an exponential moving average (alpha=0.1). Reject history samples where motion vectors indicate disocclusion.
- Spatial Filter (A-Trous Wavelet): A 5-iteration edge-aware spatial filter that progressively increases its kernel radius (1, 2, 4, 8, 16 pixels). The filter weights are guided by depth, normal, and luminance edges to preserve geometric detail.
- Firefly Suppression: Clamp outlier pixels that exceed 3x the local neighbourhood median. Applied before spatial filtering to prevent fireflies from spreading.
struct DenoiserSettings
{
float temporalAlpha = 0.1f; // Blend factor for temporal accumulation
int spatialIterations = 5; // A-Trous filter iterations
float depthSensitivity = 1.0f; // Edge-stopping on depth
float normalSensitivity = 128.0f; // Edge-stopping on normals
float luminanceSensitivity = 4.0f; // Edge-stopping on luminance
bool fireflySuppress = true;
};DXR ray tracing integrates with the existing deferred lighting pipeline:
- Reflections replace or supplement screen-space reflections (SSR). The system blends between SSR and RT reflections based on the confidence of the screen-space result and available GPU budget.
- Shadows replace shadow maps for the primary directional light and optionally for point/spot lights. RT shadows provide correct contact-hardening soft shadows without cascaded shadow map artefacts.
- Ambient Occlusion replaces or blends with SSAO/HBAO. RT AO captures large-scale occlusion that screen-space methods miss.
- Global Illumination provides single- or multi-bounce indirect lighting, replacing baked lightmaps for dynamic scenes.
// In the lighting pass
if (dxr.IsFeatureEnabled(RTFeature::Reflections))
reflectionSRV = dxr.GetReflectionOutput();
else
reflectionSRV = ssrSystem.GetOutput();
if (dxr.IsFeatureEnabled(RTFeature::Shadows))
shadowMask = dxr.GetShadowOutput();
else
shadowMask = shadowMapSystem.GetShadowMask();SparkEngine uses a hybrid approach where rasterisation handles primary visibility (G-buffer) and ray tracing handles secondary effects. This is more efficient than full path tracing and allows graceful degradation:
| Effect | DXR Available | DXR Unavailable |
|---|---|---|
| Primary visibility | Rasterised G-buffer | Rasterised G-buffer |
| Reflections | RT reflections | Screen-space reflections |
| Shadows | RT soft shadows | Cascaded shadow maps |
| Ambient Occlusion | RT AO | HBAO+ |
| Global Illumination | RT GI | Baked lightmaps + light probes |
The fallback is automatic: DXRManager::Initialize() queries the DXR tier. If DXR is unsupported or the user disables it, all rendering paths seamlessly revert to screen-space and raster-based alternatives.
When DXR is unavailable (no D3D12 support, DXR Tier 0, or user preference), the engine automatically switches to screen-space alternatives:
// Automatic fallback in RenderSystem
if (!dxr.IsAvailable() || !dxr.IsEnabled())
{
ssrSystem.Execute(gBuffer, depthBuffer);
ssaoSystem.Execute(gBuffer, depthBuffer);
shadowMapSystem.RenderCascades(lightDir);
}
else
{
dxr.TraceReflections(viewProj, cameraPos);
dxr.TraceShadows(lightDir);
dxr.TraceAmbientOcclusion(viewProj, cameraPos);
}No game code changes are needed; the render system handles the selection internally based on DXRManager state.
Acceleration structures can consume significant GPU memory. SparkEngine manages this through:
- Memory Pool: A dedicated GPU memory allocator for BLAS and TLAS buffers, using placed resources in a large heap to reduce allocation overhead.
- Compaction: After initial BLAS builds, the engine queries the post-build compacted size and copies the BLAS to a tighter buffer. This typically saves 40-60% memory.
-
Budget Tracking:
DXRStats::accelerationStructureMemorytracks total AS memory. The system warns when memory exceeds a configurable budget (default: 512MB). - LOD-Based BLAS Selection: Only the current LOD mesh is included in the BLAS. When an object's LOD changes, the old BLAS is released and a new one is built.
- Streaming: Distant objects can be excluded from the TLAS entirely using the instance mask, reducing both memory and traversal cost.
auto stats = dxr.GetStats();
stats.accelerationStructureMemory; // Total AS memory in bytes
stats.blasCount; // Number of active BLAS
stats.tlasInstanceCount; // Instances in current TLAS
stats.compactionSavingsBytes; // Memory saved by compactionDXR integrates with the ECS through dedicated components:
// MeshRTComponent — attached to entities that should appear in ray tracing
struct MeshRTComponent
{
uint32_t blasHandle = 0; // Handle from DXRManager::CreateBLAS()
uint32_t hitGroupIndex = 0; // Index into the hit group shader table
uint8_t rayMask = 0xFF; // Instance mask for selective tracing
bool castsShadows = true; // Include in shadow rays
bool receivesGI = true; // Include in GI calculations
bool visible = true; // Include in TLAS this frame
};
// RTSettingsComponent — attached to the camera entity
struct RTSettingsComponent
{
uint32_t enabledFeatures = static_cast<uint32_t>(RTFeature::All);
RTQualityPreset quality = RTQualityPreset::High;
};The DXRSystem (an ECS system) runs after the RenderSystem culling pass. It iterates entities with MeshRTComponent and Transform to build the TLAS instance list, then dispatches ray tracing workloads based on the active camera's RTSettingsComponent.
The DXR Raytracing panel is available in SparkEditor under Window > Rendering > DXR Raytracing:
- DXR Status: Shows the detected DXR tier, GPU name, driver version, and supported features.
- Feature Toggles: Individual checkboxes for Reflections, Shadows, AO, and GI with per-feature enable/disable.
- Quality Preset Selector: Dropdown for Low/Medium/High/Ultra presets, with detailed SPP and bounce settings shown below.
- Denoiser Controls: Sliders for temporal alpha, spatial iterations, and edge-stopping sensitivities.
- Memory Budget: Bar graph showing current AS memory usage versus the configured budget.
- GPU Timings: Per-feature GPU timing bars (reflections, shadows, AO, GI, denoising, TLAS build).
- Debug Visualisation: Overlay modes to display raw noisy output (pre-denoise), denoised output, heat map of ray traversal steps, and acceleration structure wireframes.
// Editor panel registration
EditorPanelRegistry::Register<DXRSettingsPanel>("DXR Raytracing",
EditorPanelCategory::Rendering);- Enable only the RT features you need. RT reflections + RT shadows is a good starting point; add AO and GI only if the GPU budget allows.
- Use the lowest quality preset that looks acceptable. The visual difference between Medium and High is often subtle, but the performance cost doubles.
- TLAS rebuild is cheap; prefer rebuilding over refitting the TLAS since the instance count typically fits in cache.
- For BLAS, prefer refit for animated meshes and full rebuild for newly spawned geometry.
- Shadow rays with
ACCEPT_FIRST_HITare 2-5x faster than closest-hit rays. Always use this flag for shadow and AO rays. - Compaction saves significant memory but requires an extra copy. Schedule compaction during loading screens or when the scene is idle.
- On GPUs with limited RT hardware (e.g., GTX 1060 with software DXR fallback), consider disabling RT entirely and using screen-space effects.
dxr.status — Show DXR state
dxr.enable <feature> — Enable reflections/shadows/ao/gi
dxr.quality <preset> — Set low/medium/high/ultra
dxr.denoise <on|off> — Toggle denoising passes
dxr.debug <mode> — Visualise raw/denoised/heatmap/bvh
dxr.budget <MB> — Set AS memory budget
dxr.stats — Print detailed GPU timing and memory stats
- Rendering and Graphics -- Main rendering pipeline and deferred shading
- Shader Pipeline -- How HLSL shaders are compiled and managed
- Post-Processing -- Screen-space fallback effects (SSR, SSAO)
- Upscaling System -- DLSS/FSR upscaling that pairs with RT
- Entity-Component-System -- ECS component reference
- Physics -- Mesh collision data shared with BLAS construction
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