Talos docs

Lighting

Since 0.7Changed in next

How a Talos scene is lit in the HD renderer, what each part costs, and what it cannot do yet.

Plain text

How a Talos scene is lit in the HD renderer, what each part costs, and what it cannot do yet. Four parts make up the "lighting" package:

  1. Global illumination (GI): bounced light.
  2. The sky: sun, air, clouds, fog, night.
  3. The default look: tonemap, exposure, grade.
  4. Ray tracing: ray-traced sun shadows and reflections.

All four are Beta (docs/FEATURES.md). Field-level reference for the sky and fog is docs/SKY.md; the render settings are in docs/RENDERING.md. GI, ray tracing and the cloud layer are HD only: the standard renderer ignores them (hd-only-ignored).

How to turn things on, off and tune them

Two places:

  • The scene (meta.environment, meta.render): what the world is (sky, fog, GI strength, exposure, look).
  • The project's quality request (render.quality in the project manifest, HD only): how much work the renderer does. A preset (low, medium, high, ultra, custom, auto) plus overrides for single knobs. The default is {"preset": "auto", "targetFps": 60}. Auto measures your scene on your GPU at start-up and picks the highest preset that fits the frame budget (80% of 1/targetFps), then steps down or back up as the scene changes. With no GPU timing it falls back to a guess by GPU class. This is windowed play only; a headless run or capture is fixed to High.
"render": { "profile": "hd",
  "quality": { "preset": "high", "overrides": { "rayTracing": { "reflections": false, "giProbeUpdates": 2048 } } } }

What the presets set for GI and ray tracing:

KnobLowMediumHighUltra
rayTracing.gioffoffonon
rayTracing.giProbeUpdates (256..16384)1024204840968192
rayTracing.giCascades (1..4)2334
rayTracing.shadowsoffoffonon
rayTracing.reflectionsoffoffonon
rayTracing.reflectionResolutionhalfhalfhalffull
rayTracing.reflectionMaxRoughness0.40.50.50.7
rayTracing.windDeformoffoffoffon
rayTracing.giAlwaysoffoffoffoff
rayTracing.giGatherResolutionhalfhalfhalfhalf

GI is skipped on a frame with nothing it could light: every surface unlit (a 2D game's sprites, tilemaps and unlit user shaders) and no virtual geometry, splats, particles, water, ground cover, decals or fog volumes. The frame is pixel for pixel the same (GI lights nothing there), 0.6 to 1.2 ms cheaper on an RTX 3080 at 1080p. The first frame with lit content turns GI on for the rest of the run (switching GI rebuilds pipelines, so it never goes back off). rayTracing.giAlways: true keeps GI on from the first frame.

rayTracing.giGatherResolution (half | quarter, optional, default half; every preset says half) sets the GI's final gather: one ray per 2x2 render pixels, or per 4x4 with quarter. quarter is opt-in: the gather, guided-sampling tiles, denoiser and upsample run at a quarter of the render resolution per axis (a quarter of the rays), and the upsample reconstructs render resolution. It is softer on thin geometry (blades, twigs): on llvmpipe its GI differs from half by 7 to 12% mean per pixel in the test scenes. The radiance cache is unchanged.

Every ray-traced knob needs a GPU with ray query. Without it the knob falls back to the screen-space path and the engine says quality-unsupported-on-device; if the device or build has no ray tracing at all it says render.hd.rt_unavailable and continues (docs/RUNTIME-CODES.md).

Global illumination

HD's GI is ray traced: a probe cache (radiance probes in giCascades nested levels, giProbeUpdates of them re-traced every frame), a per-pixel final gather from it, and a temporal denoiser. It lights surfaces with sky and sun light that bounces off other surfaces, and occludes sky light in corners. It is on in the High and Ultra presets on a ray-query GPU, and off in Low and Medium (which use the sky's image-based light and ambient occlusion only).

ToDo
Turn it offrender.quality.overrides.rayTracing.gi: false
Settle changes faster after a light changeraise rayTracing.giProbeUpdates (more cost)
Reach further from the cameraraise rayTracing.giCascades (1..4)
Brighten or dim all bounced lightmeta.environment.gi.indirectIntensity, 0..16, default 1 (changing it at run time does not restart the GI)
Brighten or dim it in one placea giVolume component: size, indirectIntensity 0..16, mode camera (default) or region, blend soft edge in metres (default 0.5); up to 8 volumes per mode, overlapping volumes multiply
Make one light bounce more or lessthat light's indirectIntensity
Light a moving interior (a car's cab, a lift) without blotches or leaksa giVolume that fills it, child of the moving entity, with probeSpacing (0.25 suits a cab): it carries its own GI probes, which move with it (RENDERING.md)

Details of the controls: RENDERING.md, "GI controls (HD)".

The sky

The HD renderer's physical sky is "kind": "atmosphere" (Rayleigh and Mie scattering, ozone, multiple scattering). The sun is wherever you put it, and the sky, the sun's colour, the image-based light and the aerial perspective all follow. Full fields in SKY.md; the short version:

  • Time of day. On an atmosphere sky the preset (clear-noon, golden-hour, overcast, dusk, night) sets the sun, a cloud layer, the air and a fog together, wherever the scene wrote nothing of its own. Anything the scene writes wins field by field.
  • Clouds. sky.clouds: preset (clear, fair, broken, overcast, storm), coverage 0..1, altitudeM, thicknessM, density, windSpeedMps, windDirectionRad, shadowStrength. They cast shadows on the ground (see render.hd.cloud_shadow_unavailable). Thick cloud lets through only a few percent of the sun, so a scene with little ambient goes nearly black under it: lower shadowStrength (0..1, default 1, 0 no shadow) to keep the ground lit. It sets one map, so the ground, water, fog, particles and ray-traced hits all follow; the clouds themselves look the same. It softens the sun's light only. The ambient under a cloud is unchanged, so a scene that is dark there because its ambient is low is still dark in the parts the sun doesn't reach, and any soft mottling in that ambient stays; start around 0.5-0.7 rather than at 0, which removes cloud shadows altogether. They drift with the simulation clock. Quality knobs: clouds.resolution, clouds.march, clouds.steps, clouds.shadowMapSize, clouds.detail, and for sculpted clouds (sky.clouds.style) clouds.sharpness and clouds.fineDetail.
  • The air. sky.atmosphere: preset (earth-clear, hazy, polluted, stylised), haze 0..10, visibilityKm 0.1..1000 (overrides haze; 50 clear, 25 typical, 10 hazy, 1 fog), groundAlbedo 0..1 (default 0.3), and aerialScale 0..100 (default 1 is physical; larger values give a small world the layered blue depth of a big one, without changing the sky itself).
  • Fog. meta.environment.fog with a preset (light-haze, valley-mist, morning-mist, dense, aerial) or a visibilityM. aerial (20 km visibility, thin within about 250 m of height) is the depth cue for a 0.3 to 1 km scene, and the one to use under a panorama sky (see Known limits). A bare fog: {} is 0.02 per metre, which is thick.
  • Night. With the sun below about -4 degrees on an atmosphere sky, HD adds a moon light (the directional light, opposite the sun, 35 degrees up) and a dark blue dome so the image-based light and shaded sides are not black. Both fade in from -4 to -10 degrees. Auto exposure lifts a night frame at most 2 stops; pin exposure for a darker night.

Shadow tint (standard renderer)

The standard renderer lights a surface the sun cannot reach with the sky's light only, so a shadow on a dark surface (a dark road) goes near black. A directional light can lift its shadows with an optional shadowTint:

{ "type": "light", "kind": "directional", "color": "#FFF1D6", "intensity": 3,
  "shadowTint": { "color": "#5B7FA6", "strength": 0.45 } }

A fully shadowed surface then receives strength of the sun's light in color (the sun term is sun * (shadow + tint * (1 - shadow))), scaled by the surface's albedo and its angle to the sun, on top of the sky light. A surface the sun reaches is unchanged, and so is a scene without the field (the frame is byte for byte the same). strength is 0..1 and color a hex colour (light-shadow-tint-range); on a point or spot light it does nothing (light-shadow-tint-ignored). It applies to the lit shading models (pbr, toon, vertex); unlit materials, custom shaders and the HD renderer ignore it. A face turned away from the sun gets none of the sun's light, tinted or not, so the tint shows where a cast shadow falls on a surface that faces the sun, which is where the sun's shadow reads as black.

The default look

A scene that writes nothing gets:

  • Tonemap agx-punchy in both renderers (meta.render.tonemap; others: agx, aces, neutral, none, HD's unreal-filmic).
  • Auto exposure in HD (exposureMode: "auto", metered every frame), unless the scene sets exposure or the retro style. exposure then shifts the metered result by log2(exposure) stops. For a fixed exposure write "exposureMode": "manual" (with exposure, default 1).
  • No grade. meta.render.look absent draws no grade, the same as { "preset": "natural" }.

To grade, name a look and tune it:

"render": { "look": { "preset": "cinematic", "contrast": 1.1, "temperatureK": 6000, "shadowsLift": -0.3 } }

Presets natural, cinematic, bright-day, overcast, night, stylised; overrides contrast 0.5..2, saturation 0..2, temperatureK 2000..15000, tint -1..1, shadowsLift -1..1, vignette 0..0.9 (HD). See RENDERING.md, "Scene look".

Ray tracing

Ray-traced sun shadows replace the shadow cascades, and ray-traced reflections replace the screen-space ones. Both need a GPU with ray query. The same ray-tracing core is what GI traces against, so a mesh that is not in it casts and receives neither.

  • On or off: rayTracing.shadows and rayTracing.reflections (table above). Both are on in High and Ultra on a ray-query GPU and off in Low and Medium. To keep them off in a project that runs High, override them to false.
  • Size floor. The HD setting rt_min_radius_m leaves instances smaller than that bounding radius (ferns, weeds, debris) out of ray tracing. The default is 0 (off: everything is traced). It is an engine setting, not a scene or project field yet; the capture tool sets it from its manifest.
  • Self-shadowing. Virtual-geometry meshes are traced as a simplified copy that strays centimetres from what is drawn. A sun-shadow ray leaving a drawn surface passes over that copy's back faces within its measured deviation (the surface's own mesh, seen from inside), so a double-sided scanned rock does not shadow itself in stripes. Front faces always count, so foliage keeps its own shade. One exception: a tree with leaves as geometry (leafMesh) is traced as fewer, larger stand-in leaves, and a stand-in leaf within that distance is passed over from either side, because it lies among the real leaves it stands for. Bounce light passes a stand-in leaf two times in five, so the shade inside such a crown keeps some light (docs/status/hd-leaf-proxy.md).
  • Wind deformation. rayTracing.windDeform (Ultra only by default) makes swaying foliage trace the exact deformed shape, about 0.7 ms in a dense forest (from the knob's own description).
  • Glass reflections. rayTracing.reflectionResolution sets how glass (see-through surfaces with roughness 0.3 or less) traces its mirror reflection. half (Low to High): only the nearest glass pane traces, one ray per pixel in a pass of its own; both faces of that pane (a modelled pane has two, up to 5 cm apart along the view) show it, and glass further behind shows the sky's prefiltered reflection. full (Ultra): every glass layer traces in the forward pass. Both trace at render resolution, as opaque surfaces' reflections do (despite the knob's name). The engine setting rt_glass_reflections: FrontLayerHalf traces the front pane at half resolution, one ray per 2x2 pixels; no knob value picks it yet.

What it costs

RTX 5080, 1920x1080, High (all ray-traced effects on), median GPU time per frame spent in the GI and RT passes, measured 16 Oct 2026 on the gate's reference scenes (raw numbers: the per-pass budgets in scripts/gate/perf/gpu-cells.json, added with the gate's advisory per-pass check). Provisional: the gate does not enforce these yet, and they will move as GI changes.

SceneGI (probe select, trace, blend, gather, denoise)RT shadows and reflectionsTotal
Open water, UI gallery0.14 ms0.04 msabout 0.18 ms
Driving course0.37 ms0.05 msabout 0.42 ms
Alpine terrain0.32 ms0.11 msabout 0.43 ms
Dense forest (one camera)0.53 ms0.18 msabout 0.71 ms
  • Low and Medium run no GI and no RT, so these passes cost nothing there. Ultra has more probe updates, a fourth cascade and full-resolution reflections and has not been measured per pass.
  • gi-probe-blend is a fixed 0.07 ms in every scene. Everything else scales with foliage and what the camera sees.
  • RTX 3080: roughly 1.7 times these numbers (an estimate, not yet measured per pass), and more in leaf-heavy frames (a gather over a frame full of leaf cards read 1.9 ms on the 3080 against 0.25 ms for opaque terrain; docs/status/hd-gi-gather-leaf2.md).
  • After a camera cut in a leaf view the GI re-converges at 1.4 to 2.2 ms for a short while (llvmpipe and 5080 lane measurements; not gated).
  • Idle cost of having the ray-tracing core built at all: about 0.05 ms and 300 to 420 MiB of GPU memory (docs/status/hd-quality.md).

2D lighting

Sprites and tilemaps are drawn as painted until they set lit; then they take the scene's lights as a surface facing the camera (a sprite) or facing +Z (a tilemap). Off, nothing about them changes.

  • Normal maps. A sprite view's normalImage, or a tileset's normalImage, is a tangent-space normal map the size of the picture it goes with, cut the same way: red +X, green +Y (up), blue out of the picture; blue below 160 is raised to it, since a picture faces the camera. A flipped sprite's normals flip with it. A map of another size is left out with a sprite-map-size warning and the picture draws flat. Maps go on a page beside the atlas page, in the same layout, so a scene with no maps spends nothing on them; with them, each page that holds one costs twice its texels.
  • Emissive. emissive on a sprite adds a colour after lighting, so it shows in the dark. emissiveImage shapes it: its brightness times its alpha is the mask. Glow does not light other things; put a point light beside it for that. Tilemaps have no emissive.
  • Lights in 2D. A 2D scene uses the ordinary light component. A point or spot light is lit in 3D, so its Z is its height over the sprites, which lie at or near Z 0: a low light rakes across a normal map and shows the relief, a high one lights evenly. range is its reach and intensity its strength, in world units as for any light. At most 8 point and spot lights reach a frame. Use lit: true on what should be lit and leave the rest painted.
  • lighting-map-unlit warns about a map or emissive on something not lit, which nothing reads.
  • No shadows. Sprites and tiles cast none in 2D, and there are no 2D occluders.

Keep lights in front of your sort layers. A flat sprite is drawn 1 m toward the camera per sortLayer, and it is lit where it is drawn. A point light at z 1.5 is behind a sprite on layer 2, which then takes only the ambient light. Put 2D lights in front of the highest layer they should reach.

samples/lantern-hollow/scenes/lamplight.scene.json puts them together: a pixel-perfect camera, a lit tilemap with a normal sheet, a lit walker whose clip raises a footstep event, and two lamps.

The pixel-perfect camera and 2D lighting work together: the art-resolution pass lights each art pixel once.

Known limits

  • GI over thin foliage is dark. Leaves are handled approximately (the gather sees the front hemisphere only), so forest floors, undergrowth and crowns read darker than they should and lack a translucent glow.
  • Probe spacing is coarse, 0.5 m, so thin walls and small gaps can leak or darken a little, and a dim room lit by a small lamp is noisy.
  • Moving interiors. The GI's probes stay put in the world, so a cab driving through them shows outside light leaking in as blotches that never settle while it moves. Give it a giVolume with probeSpacing (above).
  • Far-terrain patches. GI rays hit a coarse terrain proxy that can sit above the true surface in hollows, which gives soft darker patches (about 10%) on distant hills.
  • Noise and settling. After a camera cut or a fast light change the GI takes a couple of seconds to settle, and small-source interiors stay blotchy longer.
  • Meshes over the ray-tracing binding limit (very large scanned meshes, millions of triangles) are drawn but not traced: no ray-traced shadow, reflection or GI from them, and the log says render.hd.rt_mesh_too_large once per mesh. An automatic simplified copy for such meshes is in progress. Until then use a decimated mesh.
  • Small plants under rt_min_radius_m cast no ray-traced sun shadow when the floor is set, because with ray-traced shadows on the cascades are off. Leave the floor at 0 if small plants must shadow.
  • Captures (splat or probe captures) are not seen by ambient occlusion or GI.
  • No stars and no moon disc in the night sky: night has a moon light and a dark dome only.
  • No aerial perspective under a panorama (hdr) sky. It runs only with "kind": "atmosphere". Under a panorama use the fog preset aerial as the depth cue.
  • The terrain horizon map is off by default. Set horizon: true on the terrain component for terrain shadows past the cascades and valleys that darken when the sun sets behind a ridge (RENDERING.md).
  • Foliage and rough ray-traced reflections: thin foliage pixels skip the reflection ray for cost, so reflections of leaves are not ray traced.

Troubleshooting

Shade looks too dark

  • Ambient occlusion stacks on GI under plants and in shadow. Check ambientOcclusion.enabled in the quality overrides; lower the AO knobs or accept the known foliage limit above.
  • Raise meta.environment.gi.indirectIntensity (or place a giVolume) to lift bounced light.
  • Auto exposure meters a bright sky and leaves shadows low. Shift it with exposure (stops = log2(exposure)), or set exposureMode: "manual".
  • Low and Medium have no GI, so shadowed ground gets only the sky's flat light.
  • A closed mesh around the camera (a backdrop sphere, a room) blocks ray-traced sun shadows and traps GI inside; in a recording or capture the log says render.hd.black_frame. Give the mesh a material file with "castShadows": false.

Looks milky or washed out

  • Fog too thick: a bare fog: {} is 0.02 per metre. Write a preset or a visibilityM.
  • A time-of-day preset brings its own fog (golden-hour and overcast light-haze, dusk valley-mist). Write "fog": { "density": 0 } to remove it.
  • Air too hazy: raise atmosphere.visibilityKm, lower haze, and check aerialScale is not set above 1 for a large world.
  • A tonemap of none or a look with low contrast also flattens the image.

Banding in the sky or on distant slopes. This is not a colour-precision problem. Measured on the 5080: output dithering is on, the sky lookup is clean, and the same view with clouds off is a smooth gradient. The bands are the cloud layer's own structure: the deck has a sharp flat base, and its shadows lie flat on far slopes, most visible in a low-sun glow. clouds.steps does not help (32, 64 and 128 steps give the same image). Change clouds.preset or coverage, or set "clouds": { "preset": "clear" } to remove the layer. A softer cloud base is a planned look change.

Codes to look for (docs/RUNTIME-CODES.md, docs/RULE-CODES.md):

CodeMeans
render.hd.rt_unavailableno ray tracing on this device or build; the screen-space fallbacks are used
quality-unsupported-on-devicea ray-tracing knob fell back because the GPU cannot do it
render.hd.rt_mesh_too_largea mesh is not traced: no RT shadow, reflection or GI from it
render.hd.black_framea frame came out black, often a closed mesh around the camera
render.hd.horizon_map_unavailabledevice too limited for the terrain horizon map
render.hd.cloud_shadow_unavailableclouds draw but cast no shadow on this device
render.hd.rt-deform-over-budgetswaying instances trace their undeformed shape
render-clouds-without-atmospheresky.clouds is set but the sky is not atmosphere
hd-only-ignoreda GI, cloud or exposure field was set on the standard renderer