flutter3d
Showcase Changelog 38 packages API reference

Light channels

since 0.7.0 Shading and materials

LightNode.channels and SceneNode.lightChannels are two bit masks that meet in the middle: a light reaches an object only when light.channels & node.lightChannels is not zero. Both default to every bit set, so a scene that has never heard of channels is lit exactly as it always was. Nothing here is a check the shader makes at the last minute; a light that cannot reach an object never takes one of its eight slots either.

Step 1: Two balls, two channels #

The near ball is on channel 0, the far ball on channel 1, and a red light is restricted to channel 0 as well. Only the near ball sees it; a plain fill light keeps the far one visible.

// Channel 0 for the near ball and its own light, channel 1 for the far
// one: the mask a light and a node meet on decides whether it reaches it,
// never distance or anything the shader has to guess about.
_redBall = MeshNode(
  sphere,
  Material(baseColor: Vector4(0.8, 0.8, 0.8, 1.0)),
  name: 'near ball',
)..lightChannels = LightChannels.only(0);
final MeshNode farBall =
    MeshNode(
        sphere,
        Material(baseColor: Vector4(0.8, 0.8, 0.8, 1.0)),
        name: 'far ball',
      )
      ..setPosition(2.4, 0.0, 0.0)
      ..lightChannels = LightChannels.only(1);

_redLight = LightNode(name: 'red', type: LightType.point, intensity: 6.0)
  ..color.setValues(1.0, 0.25, 0.2)
  ..channels = LightChannels.only(0)
  ..setPosition(0.0, 1.6, 1.2);

Step 2: Open the second channel #

The toggle adds the far ball's channel to the near ball's mask, so the same red light that only reached the near ball now reaches both.

// With the toggle on, the near ball opens its second bit too and the
// light starts reaching it exactly as it always reached the far one.
_redBall.lightChannels = sameChannel
    ? (LightChannels.only(0) | LightChannels.only(1))
    : LightChannels.only(0);

Step 3: What this page checks #

The claim is the mask itself: the red light's channel and the ball it is meant to light have to share a bit, and the light has to actually be restricted rather than left on every channel by accident.

if (!LightBuffer.reaches(_redLight, _redBall.lightChannels)) {
  throw StateError('the red light does not reach the ball it lights');
}
if (_redLight.channels == LightChannels.all) {
  throw StateError('the light was never restricted to a channel');
}
if (frame.drawCalls < 2) {
  throw StateError('not every ball was drawn');
}

Tip. LightChannels.only(n) is the nth bit, counting from zero. LightChannels.all and LightChannels.none are the two values a caller does not have to invent themselves.