Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lighting

Lights in Flint are ordinary entities with a light component. The renderer reads them every frame, sorts them by name so the shadow-casting sun is stable across reloads, and feeds them to the PBR shader alongside a set of scene-wide shading levers authored in the [environment] block. This page owns all of that: the light component, cascaded and contact-hardening shadows, area lights, and the “clay look” levers.

The Light Component

[entities.sun.light]
type = "directional"
direction = [0.5, 1.0, 0.3]        # points toward the light
color = [1.0, 0.98, 0.95]
intensity = 3.0
angular_size = 0.5                 # degrees; drives PCSS penumbra

[entities.lantern.light]
type = "point"
color = [1.0, 0.7, 0.4]
intensity = 8.0
range = 12.0
source_radius = 0.15               # meters; softens the specular hotspot

[entities.spot.light]
type = "spot"
direction = [0.0, -1.0, 0.0]
color = [1.0, 1.0, 1.0]
intensity = 20.0
range = 15.0
inner_angle = 0.3                  # radians
outer_angle = 0.5
source_radius = 0.1

Point and spot lights sit at their entity’s world position, so they follow transform hierarchies and animation like any other entity. Directional lights ignore position.

FieldApplies toTypeDefaultDescription
typeallstring"directional"directional, point or spot
colorall[f32; 3][1, 1, 1]Linear RGB
intensityallf321.0Radiance multiplier
directiondirectional, spot[f32; 3][0, -1, 0]For directional lights this points toward the light; for spots it is the cone axis
angular_sizedirectionalf320.0Apparent size of the source in degrees (sun about 0.5, softbox 2–5). 0 keeps hard 3x3 PCF shadows (ADR 0056)
range (alias radius)point, spotf3210.0Falloff distance in world units
source_radiuspoint, spotf320.0Physical radius of the emitter in meters; 0 = punctual (ADR 0056)
inner_anglespotf320.3Full-intensity cone half-angle, radians
outer_anglespotf320.5Cone edge half-angle, radians
volumetric_intensitydirectionalf320.0God-ray strength for this light; see Post-Processing
volumetric_colordirectional[f32; 3]light colorTint of the shafts

There is no light.toml in schemas/components; the renderer reads these keys straight from the component table, so flint validate will not catch a typo in a light field. Scenes that need a schema can add one in their own schemas/ layer.

Limits: the shader takes a fixed number of directional, point and spot lights. Only one directional light casts cascaded shadows: the one with the highest intensity, with entity-name order breaking ties (ADR 0045), so a fill light can never steal the sun’s shadows because of how it was named. If no scene light exists at all, the renderer falls back to a built-in warm key light and cool fill.

Area Lights

source_radius on point and spot lights, and angular_size on directional lights, turn punctual lights into small area sources (ADR 0056). The specular lobe uses the representative-point approximation: effective roughness widens by source_radius / (2 * distance) and shading distance never falls inside the source, so the hotspot on a glossy floor becomes a soft disc instead of a pinprick. Diffuse lighting is unaffected. A value of 0 takes the original code path exactly.

Shadows

Directional lights cast shadows through cascaded shadow maps: several cascades cover increasing distance bands from the camera, giving crisp shadows nearby and broad coverage far away. Shadows can be disabled per launch with --no-shadows, and toggled at runtime in the Rendering & Effects menu (F4).

Resolution. --shadow-resolution defaults to 2048 texels per cascade. Before ADR 0049 the shader hardcoded a 1/2048 texel size, so any other resolution filtered incorrectly and the flag was effectively decorative. The texel size is now uploaded with the cascade uniforms, so 512, 1024 and 4096 are real choices; the F4 menu’s Shadows section rebuilds the shadow pass when you pick one.

Contact hardening (PCSS). When the shadow-casting directional light has a non-zero angular_size, the shader switches from a fixed 3x3 PCF kernel to percentage-closer soft shadows (ADR 0057): a Vogel-disk blocker search estimates the average occluder depth, the penumbra width grows with the occluder-to-receiver distance and tan(angular_size), and the filter kernel is sized to match. Shadows are sharp where an object touches the ground and soften as it lifts away. angular_size = 0 takes the legacy PCF path verbatim, which keeps existing renders byte-identical.

Skinned casters. Skinned meshes cast shadows through a dedicated vs_skinned_shadow entry point that applies bone transforms before the depth write.

The [environment] Block

Scene-wide shading lives in [environment], next to the skybox path. Every lever here follows the same convention: absent or zero means the exact original shading, so scenes that never set them look the way they always did.

[environment]
skybox = "textures/dusk.hdr"
ambient_sky = [0.12, 0.13, 0.18]
ambient_ground = [0.06, 0.05, 0.04]
diffuse_wrap = 0.3
oren_nayar = 0.7
sheen_color = [1.0, 0.9, 0.8]
sheen_strength = 0.15
FieldTypeDefaultDescription
skyboxstringnoneEquirectangular panorama for the sky
ambient_sky[f32; 3][0.12, 0.13, 0.18]Hemisphere ambient, upper half (linear RGB)
ambient_ground[f32; 3][0.06, 0.05, 0.04]Hemisphere ambient, lower half
diffuse_wrapf320.0Softens the diffuse terminator; 0.2–0.5 reads as matte or faintly subsurface
oren_nayarf320.0Blend from Lambert toward the Fujii Oren-Nayar approximation, 0–1. Roughness supplies sigma; this only blends (ADR 0048)
sheen_color[f32; 3][1, 1, 1]Charlie-sheen rim tint
sheen_strengthf320.0Rim strength. No energy compensation, so keep it at or below about 0.3 (ADR 0048)

Fog is not here; it lives in [post_process].

How they combine in the shader: hemisphere ambient interpolates between the two colors by the surface normal’s Y. Diffuse wrap replaces the raw n·l term; Oren-Nayar scales the diffuse magnitude; the Charlie sheen adds a rim lobe on top. The three are orthogonal, and when all are zero the shader takes the original code path. The wrap and Oren-Nayar values ride the w components of the ambient uniforms encoded as 1 + value, which is why a stale or default uniform (w = 0) still means “off”.

The clay look

ADRs 0042 through 0052 grew these levers to give a scene a soft, sculpted, matte “clay” reading without touching materials. A recipe that works on the tavern showcase:

Clay levers off

The tavern with default shading: Lambert diffuse, sharp terminator, neutral grade.

Clay levers on

The same frame with Oren-Nayar 0.7, sheen 0.15, and a warm grade gain.

[environment]
diffuse_wrap = 0.25
oren_nayar = 0.7
sheen_color = [1.0, 0.9, 0.8]
sheen_strength = 0.15

[post_process]
ssao_samples = 16
grade_lift = [0.03, 0.02, 0.015]
grade_gain = [1.04, 1.0, 0.94]
film_grain = 0.02

The same levers are available headlessly, and CLI values win over the scene block:

flint render scene.toml --oren-nayar 0.7 --sheen-strength 0.15 --sheen-color 1,0.9,0.8 \
    --grade-gain 1.04,1,0.94 --ssao-samples 16

Runtime Control

The Rendering & Effects menu’s Lighting section exposes all six [environment] levers with a Reset button, plus shadow enable and resolution under Shadows. In code, SceneRenderer offers set_ambient / reset_ambient, set_diffuse_wrap, set_oren_nayar, set_sheen, set_shadow_resolution, and lighting_levers() to read the current state back for seeding UI.

Scripts do not currently have bindings for these levers; they are a per-scene look, not a per-frame effect. For per-frame mood changes use the sticky post-process overrides on the Post-Processing page.

Further Reading

  • Rendering — the PBR pipeline these lights feed
  • Post-Processing — volumetric shafts, grade and grain
  • Sky — the procedural sky, which carries its own ambient_sky / ambient_ground on the sky component, distinct from the [environment] keys here
  • File Formats — the [environment] block reference