flutter3d
Showcase Changelog 38 packages API reference

Directional, point and spot lights

since 0.2.0 Shading and materials

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');
}