Lights in lumens and lux
Open the live demo · Read the source · View on GitHub
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');
}