Skip to content

lights: color+intensity+exposure ergonomics + candela/lumen input variants #176

Description

@havokentity

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:

  1. Color × scalar-intensity decomposition — separate color (RGB chromaticity) from intensity (scalar brightness). Doubling brightness is one parameter change.
  2. exposure parameter — log-space multiplier in stops (camera EV). exposure=+1 = 2× brighter, intuitive when balancing scenes.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions