flutter3d
Showcase Changelog 38 packages API reference

Lights in lumens and lux

since 0.7.0 Shading and materials

LightNode.intensity is a plain number with no unit of its own. Photometric fixes an exchange rate for it: an 800 lumen point lamp, the ordinary bulb that replaced a sixty-watt incandescent, converts to an intensity of 1.0 at a metre. Every other conversion follows from that by arithmetic, so a lamp off a datasheet can be typed in directly instead of tuned by eye.

Step 1: A sky in lux #

A directional light has no position and no falloff, so its intensity is the illuminance a facing surface reads, anywhere in the scene. Overcast daylight is about ten thousand lux.

// A directional light has no falloff, so its intensity is what a surface
// facing it reads: lux is the unit its own datasheet would give.
_sky = LightNode(name: 'sky', intensity: Photometric.fromLux(overcastLux))
  ..setLocalForward(Vector3(-0.3, -0.7, -0.5));

Step 2: A lamp in lumens #

A point lamp spreads its flux over the whole sphere around it, so Photometric.fromLumens divides by 4π before converting to intensity.

// An 800 lm bulb is the ordinary sixty-watt incandescent it replaced.
_lamp = LightNode(
  name: 'lamp',
  type: LightType.point,
  intensity: Photometric.fromLumens(lampLumens),
  range: 8.0,
)..setPosition(1.6, 1.2, 1.0);

Step 3: Move the sliders #

Both lights are recomputed every frame from whichever unit their slider is in, so raising the lux slider is the same as raising the sun outside a window.

_sky.intensity = Photometric.fromLux(overcastLux);
_lamp.intensity = Photometric.fromLumens(lampLumens);

Step 4: What round-trips #

Photometric.toLux and Photometric.toLumens are the inverse of the two conversions above. Feeding a light's intensity back through them should return the exact number the slider set, and that is what this page checks.

const double epsilon = 1e-6;
if ((Photometric.toLux(_sky.intensity) - overcastLux).abs() > epsilon) {
  throw StateError('the sky intensity does not convert back to its lux');
}
if ((Photometric.toLumens(_lamp.intensity) - lampLumens).abs() > epsilon) {
  throw StateError('the lamp intensity does not convert back to lumens');
}
if (frame.drawCalls < 1) {
  throw StateError('the ball was not drawn');
}