GPU-driven accumulation path-traced renderer for OpenPBR materials.
Theia — Titaness of heavenly light, mother of Helios, Selene and Eos.
Theia is a modern Vulkan 1.4 renderer built on GPU-driven rendering techniques.
It implements the OpenPBR Surface v1.1.1 material model and converges to Hyperion's path-traced output on identical test scenes through frame accumulation. A unified HW ray-traced GI pass (sharing Hyperion's path_integrator) provides multi-bounce indirect light and transmission/refraction. See Indirect lighting and GI architecture below.
Converges to the ground truth — accumulates jittered path-traced samples and resolves to the Hyperion reference.
Architecture driven by GPU-Driven Rendering — compute-based culling, indirect dispatch, and clustered lighting.
Same 30 test scenes as Hyperion — converged to visual parity through accumulation. Theia at 256 frames, 1280×720.
| cornell_classic | cornell_classic_rec709 | cornell_empty |
|---|---|---|
![]() |
![]() |
![]() |
| cornell_spheres | cornell_suzanne | cornell_textured_cube |
|---|---|---|
![]() |
![]() |
![]() |
| openpbr_metals | openpbr_dielectrics | openpbr_coat |
|---|---|---|
![]() |
![]() |
![]() |
| openpbr_fuzz | openpbr_specular | openpbr_organics |
|---|---|---|
![]() |
![]() |
![]() |
| openpbr_thinfilm | openpbr_advanced | shaderball_base |
|---|---|---|
![]() |
![]() |
![]() |
| shaderball_coat | shaderball_fuzz | shaderball_metal |
|---|---|---|
![]() |
![]() |
![]() |
| shaderball_opacity | shaderball_specular | shaderball_subsurface |
|---|---|---|
![]() |
![]() |
![]() |
| shaderball_thinfilm | shaderball_transmission | shaderball_emission |
|---|---|---|
![]() |
![]() |
![]() |
| shaderball_checker | shader_ball | bunny_shaderball |
|---|---|---|
![]() |
![]() |
![]() |
| camera_suzanne | ABeautifulGame | dragon_teapot |
|---|---|---|
![]() |
![]() |
![]() |
- Vulkan 1.4 dynamic rendering —
vkCmdBeginRendering(no render passes; modern efficient rendering) - GPU-driven forward rendering with VK_EXT_device_generated_commands (GD6) —
GpuCullPasscompute shader frustum-culls instances each frame; outputscompactInstanceList[]+indirectDrawBuf{visibleCount,1,1};vkCmdExecuteGeneratedCommandsEXTissues a single GPU-generated draw whose command is the GPU-writtenindirectDrawBuf, dispatchingvisibleCounttask workgroups; task shader usescompactInstanceList[gid.x]— shared entry point with thevkCmdDrawMeshTasksIndirectEXT(GD3) fallback — CPU records only, no readback - Direct lighting — 1-2 directional lights + Forward+ tile-based point light culling (16×16 px tiles, up to 128 lights/tile)
- Environment lighting — equirectangular HDR panorama sampled raw for the sky background and env-NEE (importance-sampled via a marginal/conditional CDF); specular reflections and all indirect lighting come from RT-GI (no split-sum IBL prefilter)
- Ray-traced global illumination (RT-GI) — inline
VK_KHR_ray_querycompute stage; shared unidirectional path-integrator core (NEE + MIS + Russian Roulette) in Harmonia; output feeds the accumulation → denoiser chain for convergence to Hyperion ground truth. RT-GI is always on — the single indirect/reflection/occlusion path (there is no GI-off fallback) - ReSTIR DI — spatiotemporal reservoir resampling for direct illumination: 8-candidate RIS via power-weighted emissive CDF, temporal reuse (M-cap=20, motion-vector reprojection, normal+depth validation), unbiased W = w_sum/(M·p̂), single shadow ray per pixel; feature-gated (
--no-restir-di); bias audit:cornell_classicmean_diff 2.0 vs Hyperion ground truth - Temporal Anti-Aliasing (TAA) — cross-vendor YCoCg 3×3 neighbourhood AABB clamping + 90/10 history blend; motion-vector reprojection from A1b;
vkCmdCopyImageping-pong history; runs after MotionVectorPass, before denoiser;--no-taaopt-out - A-SVGF denoiser (interactive presentation only) — fixed-radius à-trous wavelet filter that stabilizes the low-spp interactive window. It is not a converging stage (its effect scales with resolution, not sample count), so it is forced off for
--output/--no-postfx— an offscreen capture is the raw scene-referred estimator result. Pipeline order is GI → MotionVector → TAA → Accumulation → Denoiser → ToneMap, so the denoiser runs after accumulation and never feeds it; parity/convergence to Hyperion comes from accumulation alone - Sub-pixel camera jitter (Halton 2,3) — deterministic raster AA sampling for accumulation-friendly opaque edge anti-aliasing
- Interactive camera control — WASD movement, mouse look, EV100 physical exposure adjustment
All parameters follow the OpenPBR spec naming. All 8 material layers are fully supported:
| Layer | Parameters | Status |
|---|---|---|
| Base | base_weight, base_color, base_diffuse_roughness, base_metalness |
✅ |
| Specular | specular_weight, specular_color, specular_ior, specular_roughness, specular_roughness_anisotropy |
✅ |
| Coat | coat_weight, coat_color, coat_ior, coat_roughness, coat_darkening |
✅ |
| Fuzz | fuzz_weight, fuzz_color, fuzz_roughness |
✅ |
| Emission | emission_luminance, emission_color |
✅ |
| Thin-film | thin_film_weight, thin_film_thickness, thin_film_ior |
✅ |
| Transmission | transmission_weight, transmission_color, transmission_depth |
✅ |
| Subsurface | subsurface_weight, subsurface_color, subsurface_radius, subsurface_radius_scale, subsurface_scatter_anisotropy |
✅ real volumetric random walk (shared with Hyperion, run in the RT-GI compute stage) |
| Geometry | geometry_opacity, map_opacity |
✅ true presence weight (mix(ambient-medium, surface, α), spec §Opacity/Transparency), not a BRDF-weight approximation — the rasterizer draws a stochastic coverage sample per fragment (discarding with probability 1-α) and the RT-GI/shadow paths resolve the identical α through the shared estimator's pass-through gate + ∏(1-α) shadow transmittance. VK_EXT_opacity_micromap accelerates a textured mask's RT traversal (shaderball_checker) without changing the result |
Conductor reflectance uses the OpenPBR generalized-Schlick F82-tint model (base_color = F0, specular_color = 82° tint). Specular and coat microfacets use GGX with the spec's anisotropy remapping plus Turquin/Kulla-Conty multiple-scattering compensation.
Thin-film iridescence uses the spec model — a faithful port of MaterialX mx_fresnel_airy (Belcour & Barla 2017): a full s/p-polarized Airy summation with the spectral Gaussian sensitivity. Metals use the true complex-IOR conductor phase ((n,k) recovered from base_color + specular_color via Gulbrandsen 2014), so anodized metals show vivid, physically-correct interference colour, blended with the dielectric Schlick interface by base_metalness. The shared BSDF lives in Harmonia, so this renders identically to Hyperion.
Fuzz/sheen is the OpenPBR spec model — a faithful port of MaterialX's Zeltner et al. 2022 "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines" (analytic LTC + directional-albedo fits, no lookup table). The sheen directional albedo also drives the physically-correct, view-dependent darkening of the layers beneath the fuzz. Shared Harmonia BSDF → identical to Hyperion.
Subsurface (bulk, non-thin-walled) runs the same chromatic volumetric random walk as Hyperion — routed through the unified RT-GI compute stage (gi.comp executes the shared hero-wavelength free-flight/scatter/boundary estimator on both primary and secondary vertices). Light refracts through the dielectric interface (Fresnel-gated), takes Henyey-Greenstein scattering steps with per-channel extinction derived from subsurface_radius × subsurface_radius_scale (single-scatter albedo = subsurface_color), and exits through the interface. Thin-walled subsurface keeps the diffuse-sheet approximation. Transmission scattering (transmission_scatter) reuses the same walk, so both match Hyperion's transport model, not just its parameters.
- Scene-referred rendering in a selectable working color space: linear Rec.2020
(default) or linear Rec.709, chosen per scene via
working_color_spacein the[render]table; assets (material colors, textures, environment maps) are converted automatically on load - Physical camera exposure via EV100 (
ev100scene keyword) - Physical environment scale via
env_unit_nits(cd/m² per EXR unit) - Tone mapping (shared Harmonia stage): AgX (Troy Sobotka), ACES RRT+ODT, Reinhard luminance, Hable / Uncharted-2 filmic
- Display output: SDR (sRGB), HDR10 (PQ/ST2084), scRGB — runtime negotiated with swapchain
- Offscreen output: EXR is the scene-referred, untonemapped frame; PNG is tone-mapped through the same GPU ToneMapper stage the interactive window uses (the scene's configured operator — AgX/ACES/Reinhard/Hable — into an 8-bit sRGB target), so screenshots match the live view
Identical color pipeline to Hyperion — same algorithms, same visual output (given identical lighting conditions).
- Descriptor set 1, binding 4:
COMBINED_IMAGE_SAMPLERarray (up to 1024 entries) NonUniformResourceIndexfor correct divergent access- Per-material texture maps:
map_base_color,map_normal,map_orm(packed occlusion/roughness/metalness),map_emission_color
Identical to Hyperion — the TOML-based formats parsed by
Aether: a <name>.scene.toml scene description
with companion <name>.materials.toml OpenPBR material libraries (model = "openpbr")
and geometry-only OBJ meshes:
material_libraries = ["cornell.materials.toml"]
[render]
reference = "presets/preview.render.toml" # shared preset; inline keys override
working_color_space = "lin_rec2020_scene" # or "lin_rec709_scene"
[camera]
reference = "presets/cornell.camera.toml" # translate / rotate / vfov / ev100
[tonemap]
tonemapper = "agx" # aces | agx | reinhard | hable
[[geometry]]
type = "instance" # instance | box | sphere
mesh = "cornell.obj"
materials = { Floor = "WhiteWall", LeftWall = "RedWall", RightWall = "GreenWall" }OBJ files contribute only geometry; all material assignments are declared in the scene file. See the Aether README for the full format reference.
Theia is the accumulation path-traced renderer in a family of four repositories:
flowchart LR
A["Aether<br/>file format"] --> H["Harmonia<br/>shared Vulkan lib"]
H --> Hy["Hyperion<br/>path tracer · ground truth"]
H --> T["<b>Theia</b><br/>accumulation renderer"]
| Repository | Role |
|---|---|
| Aether | GPU-agnostic file formats & scene data (.scene.toml / .materials.toml / OBJ → plain CPU structs); no Vulkan |
| Harmonia | Shared Vulkan foundation reused 1:1 by both renderers — harmonia::App host, core/context, presentation, color management, tonemapping, bindless textures, shared GPU types, Slang shader build |
| Hyperion | Offline path tracer (ground truth) |
| Theia | This repo — GPU-driven accumulation path-traced renderer |
Theia consumes Aether and Harmonia via CMake FetchContent. The demo application is a
thin subclass of the shared harmonia::App host: Harmonia owns the window, swapchain,
HDR target, tonemapping/presentation, IBL probe and scene loading, while Theia injects its
renderer through the harmonia::IRenderer seam (Hyperion does the same). Slang shaders
are compiled at build time by Harmonia's shared compile_slang_shaders CMake rule
(shaders/*.slang → build/shaders/*.spv) and loaded through Harmonia's SPIR-V loader.
The GPU scene layout is renderer-specific: Theia owns its own Scene, GpuInstance
and GpuMeshlet (src/theia/scene/) built around meshlets and the mesh-shader pipeline,
distinct from Hyperion's index-buffer / ray-tracing layout. Only code shared 1:1 lives in
Harmonia.
Requirements: Vulkan SDK 1.4, CMake 3.28+, Ninja, clang-cl, vcpkg.
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang-cl \
-DCMAKE_CXX_COMPILER=clang-cl \
-DCMAKE_TOOLCHAIN_FILE="<vcpkg-root>/scripts/buildsystems/vcpkg.cmake"
cmake --build build# Interactive window (default scene: cornell_classic)
build/theia.exe --scene cornell_classic
# Offscreen render → EXR (scene-referred) + PNG (tonemapped), then exit
build/theia.exe --scene cornell_classic --output out.exr
# Camera controls
# WASD — move camera
# Right mouse — hold to look around
# Q / E — move down / up
# [ / ] — decrease / increase exposure (EV100)
# ESC — release mouse capture, then quit| Flag | Default | Description |
|---|---|---|
--scene <name> / -s |
cornell_classic.scene.toml |
Scene name or path; bare names resolve against the assets directory (also accepted as first positional argument) |
--output <file> / -o |
— | Offscreen mode: render and save EXR (untonemapped) + PNG (tonemapped), then exit |
--offscreen-frames <n> |
4 |
Number of frames accumulated/warmed up before offscreen capture is written |
--width <n> |
1024 | Render width in pixels |
--height <n> |
768 | Render height in pixels |
--validation / --no-validation |
disabled | Enable / disable Vulkan validation layers |
--taa / --no-taa |
on | Interactive-window temporal anti-aliasing during camera motion. --taa is incompatible with --output (offscreen uses progressive accumulation); the two must not be combined |
--no-restir-di |
off (ReSTIR DI on) | Disable ReSTIR direct-light importance resampling (debug/baseline) |
--indirect-ambient <x> |
0.0 |
Presentation-only indirect ambient boost (scene-referred linear) |
--no-camera-jitter |
off | Disable sub-pixel camera jitter (debug/baseline comparison only) |
Theia uses a staged pipeline for indirect lighting:
| Stage | Status | Notes |
|---|---|---|
| RT-GI compute stage | Always on | VK_KHR_ray_query multibounce; shared integrator core with Hyperion; also drives transmission/refraction; feeds accumulation → denoiser. The single indirect/reflection/occlusion path |
RT-GI is the single unified indirect + transmission provider and drives both the parity and interactive paths (the split-sum IBL fallback was removed — RT-GI is the renderer).
Theia has a module test suite (
ctest) covering its renderer-specific logic; the shared BSDF/estimator/pipeline foundation is covered by the Harmonia and Aether test suites. End-to-end material correctness is validated by visual parity against Hyperion (ground truth) on the shared test scenes.Transparency parity note: Transparent surfaces route through the shared Harmonia
path_integrator(smooth-dielectric delta refraction lobe + Beer-Lambert + env-NEE) — the same estimator Hyperion uses, with side-correct dielectric exits (Fresnel/Snell/TIR) and exact pure-absorber transmittance. Residuals are localized high-energy-IBL-through-glass Monte-Carlo variance at finite frame counts, not a transport-model gap.Gate policy: keep the strict absolute gate (
mean_diff <= 4.0) for opaque/direct/SDR fixtures. For HDR transmissive fixtures, usecompare_renders.py --gate scale-aware(absolute OR relative+PSNR), which avoids brightness-dependent false fails while still rejecting large structural mismatches.
| Library | Purpose |
|---|---|
| Aether | Scene & material file formats (.scene.toml / .materials.toml / OBJ) — GPU-agnostic CPU data |
| Harmonia | Shared Vulkan foundation (harmonia::App host, core, presentation, color, tonemapping, shared GPU types) |
| Vulkan SDK | Modern Vulkan 1.4 API |
| volk | Vulkan loader |
| VMA | GPU memory allocation (via Harmonia) |
| SDL3 | Window management & surface |
| slang-math | Mathematics — via Harmonia (transitive FetchContent) |
| meshoptimizer | Meshlet generation and mesh optimization |
| OpenImageIO | Image I/O — PNG/JPEG/EXR load and save (via Harmonia; stb and OpenEXR are transitive dependencies) |
| Slang | Shader compilation (Slang → SPIR-V) |
Theia's BSDF, volumetric, color-science, and denoising references live in Harmonia's README (the shared implementation). Below: real-time-renderer-specific references only.
| Resource | Relevance |
|---|---|
| Haar & Aaltonen — "GPU-Driven Rendering Pipelines" (SIGGRAPH 2015, Advances course) | Indirect draw, GPU command generation, per-cluster culling |
| Khronos — Mesh Shading for Vulkan | VK_EXT_mesh_shader task/mesh pipeline, EmitMeshTasksEXT |
| Meshoptimizer — Arseny Kapoulkine | meshopt_buildMeshlets, meshopt_computeMeshletBounds, cone culling |
| Bitterli, Wyman, Pharr, Shirley, Lefohn & Jarosz — "Spatiotemporal Reservoir Resampling for Real-Time Ray Tracing with Dynamic Direct Lighting" (ACM TOG / SIGGRAPH 2020) | ReSTIR DI for emissive-triangle direct lighting (8-candidate RIS, unbiased W) |
| Gribb & Hartmann — "Fast Extraction of Viewing Frustum Planes from the World-View-Projection Matrix" (2001) | 5-plane frustum cull in the cull compute shader |
| Resource | Relevance |
|---|---|
| Vulkan Specification 1.4 | vkCmdBeginRendering, mesh shaders, ray queries, descriptor indexing, timeline semaphores |
| Vulkan Roadmap 2024 | Khronos-mandated feature baseline for Theia (no vendor extensions) |
| Khronos — VK_EXT_mesh_shader Specification | Task shader payload, EmitMeshTasksEXT, output topology |
| Slang Shading Language | Mesh shader [outputtopo], task payload, DispatchMesh, SPIR-V emission |
| Resource | Relevance |
|---|---|
| Wavefront OBJ | Geometry-only OBJ import (no MTL — materials are assigned in the scene TOML) |





























