Motivation
Current light_point / light_spot API takes intensity in W/sr per channel directly:
light_point <id> <x> <y> <z> <r> <g> <b> # r,g,b in W/sr
Physically correct + matches academic reference renderers (Mitsuba, PBRT), but not industry-standard for shipping renderers. Production renderers (Arnold, RenderMan, Cycles, V-Ray, Octane) universally offer:
- Color × scalar-intensity decomposition — separate
color (RGB chromaticity) from intensity (scalar brightness). Doubling brightness is one parameter change.
exposure parameter — log-space multiplier in stops (camera EV). exposure=+1 = 2× brighter, intuitive when balancing scenes.
- Unit-of-input choice — input in candela / lumens / watts and convert internally, since artists / lighting TDs think in those units.
Today, to brighten a colored point light by 2× the user has to multiply all 3 RGB channels by 2 in the cfg. To convert from a real-world bulb spec (e.g. "60W LED ~120 cd") the user has to manually compute 0.00146 cd/(W/sr) × 120 cd = 0.176 W/sr per channel. Friction.
Proposed API
Keep light_point / light_spot / light_sphere / light_quad as the canonical low-level commands (W/sr or W/m²/sr, physics-direct, no conversion). Add high-level sugar variants:
# color * intensity (color is RGB chromaticity in [0,1], intensity is scalar W/sr)
light_point_color <id> <x> <y> <z> <r> <g> <b> <intensity_wsr>
# candela in (converted to W/sr via 1 cd = 1/683 W/sr at 555nm peak)
light_point_cd <id> <x> <y> <z> <r> <g> <b> <intensity_cd>
# lumens in (converted via 1 lm = 1/(683 * 4π) W/sr for omnidirectional)
light_point_lm <id> <x> <y> <z> <r> <g> <b> <total_lumens>
# explicit exposure
light_point_exposed <id> <x> <y> <z> <r> <g> <b> <intensity> <ev>
# final = color * intensity * 2^ev
Same shape for light_spot_*. Area lights (light_sphere, light_quad) get equivalent variants in luminance units (cd/m², nits) → W/m²/sr conversion.
Internal storage unchanged
All variants populate the same AnalyticLight struct, same W/sr (or W/m²/sr for area) field. The SAVE path (cfg round-trip via SaveArchivedCvars) emits the canonical low-level form. Only the INPUT parser converts. So:
- Engine internal math stays in physics units (no conversion overhead per frame)
- Cfg files saved by the engine are deterministic + canonical
- Users can author in whatever unit feels natural
Industry reference table
| Renderer |
Decomposition |
Exposure |
Unit choice |
| Arnold |
color × intensity |
exposure (stops) |
enum: none/W/lm/cd/luminance |
| RenderMan |
color × intensity |
exposure |
lumens/cd default, radiometric switch |
| Cycles (Blender) |
color × power (W) |
implicit via Power |
Watts (assumes 4π omni) |
| V-Ray / Octane |
color × multiplier |
implicit |
enum (power/lumens/cd) |
| Mitsuba / PBRT |
intensity only (RGB) |
— |
W/sr direct (DEMONT today) |
Implementation notes
- New command registrations in
Engine.cpp next to existing light_point block. Each parses its specific unit input, computes the W/sr value, populates AnalyticLight. ~5-10 LOC per variant.
- Conversion constants:
1 cd = 1/683 W/sr at 555nm peak (luminous efficacy of 683 lm/W). Document the wavelength assumption inline; this is a one-channel approximation. For per-channel exactness we'd need a spectral conversion table, which is out of scope here.
- Unit test in
tests/ that round-trips: light_point_cd 1 0 0 0 100 100 100 50 → saved + reloaded → matches expected W/sr.
- Update
docs/integration_batch_1_test_guide.md lighting section with the new ergonomics.
Acceptance
- All 4 primitive types (
point, spot, sphere, quad) have _color, _cd (or _nits for area lights), and _lm (point/spot only) variants.
_exposed variant available for at least point and quad (the two most-common interactive cases).
- Cfg round-trip preserved: save then load produces same W/sr internal state.
- Test fixture documents 1cd → ~1.46e-3 W/sr conversion is correct within 1e-6 tolerance.
Out of scope
- Spectral colour input (xyY / CIE / blackbody Kelvin) — separate larger task.
- Per-channel luminous efficacy (vs the single-channel 683 lm/W approximation) — same, larger task.
- IES profile import (real-world light data files) — separate area-lighting wave.
Priority
Wave-2 / ergonomics. Not blocking integration→main for the current batch. Filed during user feedback on the light_tree_200_pts scene smoke test: user observed that authoring lights in raw W/sr is awkward + asked whether industry-standard renderers do this differently. They don't — they offer the ergonomic sugar this issue adds.
Motivation
Current
light_point/light_spotAPI takes intensity in W/sr per channel directly:Physically correct + matches academic reference renderers (Mitsuba, PBRT), but not industry-standard for shipping renderers. Production renderers (Arnold, RenderMan, Cycles, V-Ray, Octane) universally offer:
color (RGB chromaticity)fromintensity (scalar brightness). Doubling brightness is one parameter change.exposureparameter — log-space multiplier in stops (camera EV).exposure=+1= 2× brighter, intuitive when balancing scenes.Today, to brighten a colored point light by 2× the user has to multiply all 3 RGB channels by 2 in the cfg. To convert from a real-world bulb spec (e.g. "60W LED ~120 cd") the user has to manually compute
0.00146 cd/(W/sr) × 120 cd = 0.176 W/sr per channel. Friction.Proposed API
Keep
light_point/light_spot/light_sphere/light_quadas the canonical low-level commands (W/sr or W/m²/sr, physics-direct, no conversion). Add high-level sugar variants:Same shape for
light_spot_*. Area lights (light_sphere,light_quad) get equivalent variants in luminance units (cd/m², nits) → W/m²/sr conversion.Internal storage unchanged
All variants populate the same
AnalyticLightstruct, same W/sr (or W/m²/sr for area) field. The SAVE path (cfg round-trip viaSaveArchivedCvars) emits the canonical low-level form. Only the INPUT parser converts. So:Industry reference table
Implementation notes
Engine.cppnext to existinglight_pointblock. Each parses its specific unit input, computes the W/sr value, populatesAnalyticLight. ~5-10 LOC per variant.1 cd = 1/683 W/srat 555nm peak (luminous efficacy of 683 lm/W). Document the wavelength assumption inline; this is a one-channel approximation. For per-channel exactness we'd need a spectral conversion table, which is out of scope here.tests/that round-trips:light_point_cd 1 0 0 0 100 100 100 50→ saved + reloaded → matches expected W/sr.docs/integration_batch_1_test_guide.mdlighting section with the new ergonomics.Acceptance
point,spot,sphere,quad) have_color,_cd(or_nitsfor area lights), and_lm(point/spot only) variants._exposedvariant available for at leastpointandquad(the two most-common interactive cases).Out of scope
Priority
Wave-2 / ergonomics. Not blocking integration→main for the current batch. Filed during user feedback on the
light_tree_200_ptsscene smoke test: user observed that authoring lights in raw W/sr is awkward + asked whether industry-standard renderers do this differently. They don't — they offer the ergonomic sugar this issue adds.