# 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 ```text 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 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_enabled` with `cascades`, `cellSize` (`sdfgi_min_cell_size`), `occlusion` and `bounce` (`sdfgi_bounce_feedback`). Good for open and large scenes that change. - `"volume"`: a `VoxelGI` over the bounds of the scene, baked when it turns on (and again on a history reset). Good for small closed scenes. - `"baked"`: the `LightmapGI` nodes your game baked in the Godot editor are shown. Good for static scenes; nothing is baked at run time. - `raster.ssao: false` turns SSAO and SSIL off, `raster.msaa` sets the viewport's 3D multisampling, `profile.taa` its 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` | ## Services this kit uses (and that you can provide) This kit consumes no service from another kit.