Directional, point and spot lights
Open the live demo · Read the source · View on GitHub
Three of the engine's four light shapes leave from a single point in space, which is what "punctual" means here: the direction to the light is one vector, and the surface only has to answer one cosine. A directional light has no position at all, a point light falls off with distance in every direction, and a spot light is a point light narrowed into a cone.
Step 1: A directional light #
No position, no range, no falloff: a directional light lights every surface that faces it the same way, wherever that surface stands in the scene.
final LightNode sun = LightNode(
name: 'sun',
type: LightType.directional,
intensity: 2.0,
)..setLocalForward(Vector3(-0.3, -0.6, -0.7));
Step 2: A point light #
range bounds how far a point light reaches. Past it, the light
contributes nothing at all, which is what lets a level with hundreds of
lamps only pay for the ones actually near a surface.
_point = LightNode(
name: 'lamp',
type: LightType.point,
intensity: 6.0,
range: pointRange,
)..setPosition(0.0, 1.6, 1.2);
Step 3: A spot light #
A spot light is a point light with two cone angles added: innerConeAngle
is where the light is at full strength, and outerConeAngle is where it
reaches zero. SceneNode.lookAt aims a spot exactly the way it aims a
camera, down the node's own local -Z.
_spot = LightNode(
name: 'torch',
type: LightType.spot,
intensity: 10.0,
range: 6.0,
innerConeAngle: 0.0,
outerConeAngle: spotOuterDegrees * math.pi / 180.0,
)..setPosition(2.2, 2.2, 1.6);
_spot.lookAt(Vector3(2.2, 0.0, 0.0));
Step 4: Move the sliders #
The point light's range and the spot's cone angle are read back into the
lights every frame, so the sliders change what each light actually does
rather than a copy of it.
_point.range = pointRange;
_spot.outerConeAngle = spotOuterDegrees * math.pi / 180.0;
final Set<LightType> types = <LightType>{
for (final LightNode light in scene.lights) light.type,
};
if (types.length != 3) {
throw StateError('expected all three punctual types, found $types');
}
if (_point.range != pointRange) {
throw StateError('the point light range did not track the slider');
}
if (frame.drawCalls < 3) {
throw StateError('not every ball was drawn');
}