It's a subtle lighting feature, but in dark scenes it can make a difference.
Intensity of GI must be increased to really see it especially if there is an ambient light source.
RT hardware accelerated GI has much better performance than pure software (rasterized) GI.
GI ON, intensity cranked so effect is obvious:
GI OFF:
GI ON , yellow arrow is sun direction:
GI OFF:
Left Side GI ON / Right Side GI OFF split screen:
AI Generated Summary:
Fully Dynamic Global Illumination for Esenthel — No Baking, No Authoring
I added a complete real-time diffuse Global Illumination system to the engine’s deferred renderer.
The headline:
zero authoring — no baked lightmaps, no hand-placed probes, and no precompute step.
You flip one switch and neutral surfaces start picking up colored bounce light from the walls, terrain, and objects around them. Time-of-day is fully dynamic. The system is Vulkan-only because it needs the compute path, and it is off by default. Existing scenes render byte-for-byte identically until an app explicitly opts in.
There are two interchangeable producers that fill the lighting probes:
- []Rasterized cube capture — works on any GPU and is always available as the fallback.
[]Hardware ray-traced capture — optional ray-query path for GPUs that support it.
How it works
- DDGI-style probe grid
A camera-centered 3D grid of irradiance probes follows the camera. The deferred light pass swaps the old flat ambient term for trilinearly interpolated probe irradiance.
- Toroidal scrolling
As the camera moves, the probe volume scrolls toroidally, addressed by integer cell index, instead of re-capturing everything. This prevents a re-capture storm when moving through the world.
This also makes it floating-origin and large-world safe because the GI volume effectively rebases for free.
- Cascades
Range comes from nested cascades at geometrically increasing spacing. Three cascades cover roughly 24 m to 216 m, with a cross-cascade seam blend so there is no hard visible ring at the cascade boundaries.
- DDGI-standard atlases
Each probe captures visibility, either rasterized into a small cube or ray-traced, then compute integrates the result into octahedral irradiance and Chebyshev depth-moment atlases.
The depth moments suppress light leaking through thin walls, and a surface-normal bias helps as well.
- Decoupled capture vs. relight
Geometry capture and lighting relight are separated. Both are hard-capped per frame.
The expensive geometry capture is time-sliced and budgeted, so the cost stays bounded. A static scene captures nothing, so it is free when you are not moving. Irradiance converges through temporal blending.
- Free time-of-day relight
An optional producer bakes a lighting-independent cube once and relights it each frame with per-bounce sun shadowing. Moving the sun costs no re-capture.
- Analytic re-seeding
Scrolled-in probe slots are analytically re-seeded, which kills stale-bounce ghosting during fast movement.
- Half-resolution evaluation
Half-res evaluation plus depth-aware bilateral upscale keeps the light-pass cost down.
- Optional SSGI
A screen-space near-field bounce layer can be enabled on top of the probe GI for close-range contact GI.
Hardware ray-traced path — opt-in, additive
When ray-query hardware is present and a scene acceleration structure is registered, probe capture can be ray-traced instead of rasterized. This gives correct per-direction occlusion and bounce with no cube-face seams.
- RT occlusion moments
GI.raytrace replaces the depth-cube producer with ray-traced occlusion moments. The light pass is unchanged.
- RT bounce radiance
GI.raytrace_radiance traces the diffuse bounce itself using cosine-weighted hemisphere gathering with Fibonacci-spiral rays.
It supports textured and average-color albedo. Terrain with no UVs samples the texture’s mean color. It uses the correct hit-surface normal, NdotL, sun shadow rays, radiosity-style multi-bounce, and a per-frame sky-only cube so miss rays sample the real rendered sky.
It reaches pixel parity with the rasterized producer at the same intensity on both Cornell and world scenes.
- Zero-registration mode
GIRayTrace.auto_scene = true lets the renderer feed every opaque mesh it draws into the acceleration structure automatically.
Streamed world areas enter the TLAS as they draw and drop out as they stream away, with no app code required. Rebuilds are recorded into the frame command buffer, so there is no GPU stall.
The system is fully hardware-gated and TLAS-gated. If RT is off, unsupported, or unregistered, it falls back to rasterized capture. The software DDGI path is untouched.
Verified working on both AMD RADV and NVIDIA.
Demos
Vulkan build, under Tutorials/Source/14 - Game Basics/
Tutorial_14_DynamicGI
The basics: a neutral scene picking up colored bounce light, with live time-of-day. This is the color-bleed proof.
Tutorial_14_WorldGI
GI over a real streamed Game::World with terrain and props, plus point-light and time controls.
Tutorial_14_LargeWorldGI
GI running under the large-world floating origin. This proves it survives rebases without re-warming.
Tutorial_14_GIShowcase
The “all the knobs” demo. A Cornell room with split-screen A/B.
Controls include:
- []V — split-screen A/B
[]- / = — intensity
[]1 / 2 / 3 — cascade count
[]R — relight
[]B — Chebyshev
[]A — TAA
[]N — SSGI
[]Y — RT moments
[]U — RT radiance
[]Sun, light, and wall toggles
Tutorial_14_RTWorldGI
The payoff: ray-traced GI over a playable streamed world via the zero-registration auto-scene hook.
Controls include:
- []WASD — move
[]G — GI on/off, showing near-black ambient vs. bounced sunlight
[]U — RT radiance vs. rasterized capture
[]Y — RT moments
[]T — time-of-day
[]- / = — intensity
How it is turned off and on
Everything defaults off at the engine level. These are the relevant defaults on the global GI object in Engine/H/Graphics/GI.h, plus GIRayTrace:
GI.enabled
Default: false
Turns on the whole system. Off means legacy flat ambient.
GIRayTrace.auto_scene
Default: false
Turns on zero-registration RT scene feeding.
GI.raytrace
Default: false
Turns on ray-traced occlusion moments.
GI.raytrace_radiance
Default: false
Turns on ray-traced bounce radiance.
GI.relight
Default: false
Turns on the free-time-of-day relight producer.
GI.ssgi
Default: false
Turns on screen-space near-field bounce.
GI.capture_shadows
Default: false
Turns on sun-shadowed lit-cube capture.
GI.chebyshev
Default: true
Turns on leak suppression when GI is enabled.
GI.half_res
Default: true
Turns on half-res evaluation and upscale.
GI.auto_light
Default: true
Derives sun and sky lighting from the live scene.
Canonical app opt-in path
An app opts in from InitPre or Init:
GI.enabled = true;
GI.intensity = 2.0f;
GIRayTrace.auto_scene = true;
GI.raytrace_radiance = true;
GI.enabled is the master switch. Nothing renders GI without it.
GI.intensity controls the indirect multiplier, where 1 is physical.
GIRayTrace.auto_scene is optional and lets the renderer register the scene for ray tracing automatically.
GI.raytrace_radiance is optional and enables ray-traced bounce radiance. It falls back if unsupported.
There are also EE_GI environment-variable overrides, such as:
EE_GI=1
EE_GI_RAYTRACE_RADIANCE=1
EE_GI_INTENSITY=...
Those exist mainly for headless A/B testing. They are not the intended shipping path.
Beyond the app switch, GI is gated on backend support. GI.active() requires the Vulkan compute path. The RT path is additionally gated on ray-query hardware.
So on a non-Vulkan backend, or a GPU without ray-query support, the relevant paths simply no-op or fall back.
Net result: if you do not opt in, nothing about your scene changes.
Build and run one demo
cmake --build cmake-build-release-vulkan --target Tutorial_14_GIShowcase -j $(nproc)
EE_NO_MSG_BOX=1 ./Tutorials/Tutorial_14_GIShowcase
Documentation
Full design notes and knob reference are in:
- []The README’s Dynamic Global Illumination section
[]docs/gi-raytracing-design.md
Available in this engine fork:
https://github.com/DrewGilpin/EsenthelEngine