Advanced lighting
Usage guide of the render.raytrace kit: declare an advanced lighting mode (global illumination, ray tracing) once for every engine, its profile, the raster changes while it is active, the history resets, the hook and the raster fallback.
Advanced lighting in your game (render.raytrace) #
You declare the lighting mode of your game once; each engine applies it with its own tool. Readable offline: gripforge docs render-raytrace, or the MCP tool gripforge_docs (topic render-raytrace).
What it is for #
The kit decides whether the advanced lighting path (global illumination by ray tracing) is used, and publishes the answer; the engine applies it through its adapter: solari in the Bevy player, sdfgi in Godot, hdrp-rt in Unity, lumen in Unreal. The engine only reports facts (what it loaded, what the GPU supports); it never decides. The default is the raster rendering: a game that does not ask for anything draws as before. Where the advanced path cannot run (the web players have no ray tracing, an adapter is missing, the GPU is too old) the raster rendering takes over with a reason; the game never gets an error for it.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "render.raytrace" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "render.raytrace",
config: { "mode": "auto" } }
It requires game.engine. mode is off (default), auto (advanced where the engine reports it can) or on (asked for; the raster rendering still takes over, with the reason, where the engine cannot).
First example #
<!-- test: kits/game_engine/host/tests/render_raytrace.rs::raster_is_the_default_and_the_decision_follows_the_report --> With { "mode": "on" } and an engine that reported { engine: "bevy", target: "native", loaded: true, supported: true }, state() answers active: true, path: "raytraced", adapter: "solari", reason: "requested". If the same engine reports target: "webgpu", active is false and reason is "web-no-ray-tracing".
Configuration #
| Key | Default | Meaning |
|---|---|---|
mode | off | off, auto or on |
profile.restir | true | reuse light samples between pixels and frames |
profile.denoise | true | denoise the lighting |
profile.spatialPasses | 2 | spatial reuse passes, 1 to 3 |
profile.maxHistory | 32 | frames of history a sample may be reused for, 1 to 64 |
profile.taa | true | temporal anti-aliasing |
profile.sharpening | 0.6 | sharpening strength, 0 to 1 (0 = none) |
profile.mipBias | 0 | texture mip bias while active |
profile.gi | dynamic | kind of global illumination where the engine has the choice: dynamic, volume or baked |
profile.cascades | 4 | cascades of the dynamic global illumination, 1 to 8 |
profile.cellSize | 0.2 | size in metres of a cell of the first cascade |
profile.occlusion | false | occlusion of the indirect light by the scene (against leaks through thin walls) |
profile.bounce | 0.5 | share of the indirect light bounced again, 0 to 1.99 |
raster.msaa | 1 | multisampling while active: 1, 2 or 4 |
raster.ssao | false | keep the screen-space occlusion while active |
raster.sunShadowMaps | false | keep the sun shadow maps while active |
raster.hdr | true | high dynamic range rendering while active |
raster.probes | false | keep the raster radiance probes while active |
resetOn | engine.scene.entered, camera.cut, lighting.cut | topics that restart the temporal history |
raster is what the engine changes of its own raster rendering while the advanced path is active; when it is not, the engine keeps its own settings and state().raster is empty. With the Godot adapter (sdfgi) state().raster only carries msaa and ssao: its global illumination lights the indirect light only, so the sun keeps its shadow maps and Godot keeps its probes and HDR. Every key is moddable.
The decision #
In this order: mode off gives raster (off); no report from the engine gives raster (engine-unreported); a web target gives raster (web-no-ray-tracing); an adapter that is not loaded gives raster (adapter-not-implemented for Unity and Unreal, adapter-missing otherwise); a GPU or a renderer that does not support it gives raster (gpu-unsupported, the engine's detail says why); auto on a GPU that emulates ray tracing (the report's hardware: false: Apple M1, M2) gives raster (gpu-software-ray-tracing; on and a tap of the hook still turn it on); otherwise the advanced path is active (requested for on, auto-capable for auto).
History resets #
The advanced path accumulates light over frames. After a camera cut, a sudden change of light or a new scene that history must restart, otherwise the image ghosts. The kit listens to the topics of resetOn, raises the history generation and emits raytrace.history.reset { generation, reason } (the reason is the topic). camera.cut and lighting.cut are generic topics: a camera kit, a lighting kit, your game or a mod emits them at the right moment. resetHistory(reason) does the same by hand.
Hook, mods, service #
| What | Name | Detail |
|---|---|---|
| Hook | render.raytrace.decide | mutable active, profile, raster; cancellable (raster, reason hook); applied after the base decision |
| Moddable config | mode, profile, raster, resetOn | a mod changes them like the game does |
| Event | raytrace.changed { epoch } | the decision, the profile or the history changed; engines read state() again |
| Event | raytrace.history.reset { generation, reason } | the temporal history restarts |
A tap of the hook can never make active an engine that reported it cannot (not loaded, not supported, web).
Service render.raytrace: state(), setMode(mode), setProfile(partial | null), resetHistory(reason), reportEngine(report), engineReport().
state() answers { epoch, mode, active, path, adapter, reason, detail, profile, raster, history, adapters, engine }. path is raster or raytraced; adapters lists the adapter of each engine and whether it is implemented; engine is the last report or null.
Engines #
| Engine | Adapter | Status |
|---|---|---|
| Bevy (native) | solari | implemented |
| Godot (Forward+) | sdfgi | implemented: SDFGI, or VoxelGI / LightmapGI by profile.gi |
| Godot (Compatibility, Mobile, web export) | sdfgi | no SDFGI there: raster, reason gpu-unsupported (web-no-ray-tracing on the web) |
| Unity | hdrp-rt | not implemented yet: raster, reason adapter-not-implemented |
| Unreal | lumen | not implemented yet: raster, reason adapter-not-implemented |
| Web players (WebGPU, WebGL2) | none | raster, reason web-no-ray-tracing |
Godot #
The Godot adapter applies the decision with Godot's own global illumination, on the Forward+ renderer:
profile.gi: "dynamic"(default):Environment.sdfgi_enabledwithcascades,cellSize(sdfgi_min_cell_size),occlusionandbounce(sdfgi_bounce_feedback). Good for open and large scenes that change."volume": aVoxelGIover the bounds of the scene, baked when it turns on (and again on a history reset). Good for small closed scenes."baked": theLightmapGInodes your game baked in the Godot editor are shown. Good for static scenes; nothing is baked at run time.raster.ssao: falseturns SSAO and SSIL off,raster.msaasets the viewport's 3D multisampling,profile.taaits TAA. When the module turns the advanced path off, every value the adapter changed is given back.- History reset (
history.generation): SDFGI is switched off for one frame so Godot rebuilds its cascades; a VoxelGI is baked again. Never guessed from the camera.
On the Compatibility or Mobile renderer (and in a web export) the adapter reports supported: false with the renderer in detail: the module answers raster. The decision, the report and the adapter's counters (technique, renderer, historyResets, voxelBakes) are in the debug snapshot under render.extra.raytrace.
When it does not do what you expect #
| Symptom | Cause and fix |
|---|---|
| The image stays raster | read state().reason and state().detail: the mode is off, the engine has not reported, or it cannot |
| The light ghosts after a cut | emit camera.cut or lighting.cut, or call resetHistory |
| Godot stays raster | the project runs the Compatibility or Mobile renderer: choose Forward+ (rendering/renderer/rendering_method) |
| Godot light leaks through walls | profile.occlusion: true, a smaller profile.cellSize, or profile.gi: "volume" for a closed scene |
| Everything is too soft or too noisy | lower profile.maxHistory, raise profile.sharpening or profile.spatialPasses |
<!-- kit-services:begin -->
Services this kit uses (and that you can provide) #
This kit consumes no service from another kit. <!-- kit-services:end -->