flutter3d
Showcase Changelog 38 packages API reference

The field kept current

since 0.8.0 Light and environment

The irradiance field page fills its probes once, by casting rays, when the page opens. If the room changes after that, the probes still hold the room as it was.

With gpuUpdates above zero the renderer keeps the field current itself. Each frame it takes a few probes in turn, draws the room into a small cube from where each one stands, and folds that into the texture the lit materials read the field from. Since the room it draws is already lit by the field, every pass adds one more bounce.

Step 1: A room with a painted wall #

A grey floor, a wall along its left side, and one lamp above them. The wall's material is kept, because the page repaints it later. The scene's ambient strength is set to one: the field is read at that strength, in place of the flat ambient.

final Material floor = Material(
  name: 'floor',
  baseColor: Vector4(0.55, 0.55, 0.55, 1.0),
  roughness: 0.95,
  doubleSided: true,
);
_paint = Material(
  name: 'painted wall',
  baseColor: _colours[wallColour].clone(),
  roughness: 0.95,
  doubleSided: true,
);
final Scene scene = Scene()
  ..ambientIntensity = 1.0
  ..add(
    MeshNode(
      DeviceMesh.upload(
        context.device,
        const PlaneShape(width: 6, depth: 6).build(),
      ),
      floor,
      name: 'floor',
    ),
  )
  ..add(
    MeshNode(
        DeviceMesh.upload(
          context.device,
          const PlaneShape(width: 3, depth: 4).build(),
        ),
        _paint,
        name: 'wall',
      )
      ..setPosition(-2.0, 1.5, 0.0)
      ..setRotation(Quaternion.axisAngle(Vector3(0.0, 0.0, 1.0), -1.5708)),
  )
  ..add(
    LightNode(
      name: 'lamp',
      type: LightType.point,
      intensity: 30.0,
      range: 12.0,
    )..setPosition(-0.8, 2.5, 0.0),
  );

Step 2: An empty field that the renderer fills #

Eight probes in a 2 by 2 by 2 grid, between the floor and the lamp. Nothing is gathered here: a new field holds zeros, so it starts black. gpuUpdates is how many probes the renderer draws each frame, going round all of them in order. At two, each of the eight is looked at again every fourth frame. The room fills in over its first seconds: at the default hysteresis a probe needs about twenty-two updates to reach nine tenths of its value, which is about eighty-eight frames, a second and a half at sixty a second.

_field =
    IrradianceField(
        origin: Vector3(-1.0, 0.5, -1.0),
        spacing: Vector3(1.0, 1.0, 1.0),
        countX: 2,
        countY: 2,
        countZ: 2,
      )
      ..gpuUpdates = probesPerFrame.round()
      ..fillGutters();
scene.irradianceField = _field;

Step 3: Change the room #

Each frame the page writes the chosen colour into the wall's material and passes the two sliders on to the field.

hysteresis is how much of a probe's old value it keeps at each update, from nought to 0.99; the renderer clamps anything higher to 0.99, so a probe always takes in some of what it sees. The default is 0.9. Pick another Wall colour and watch the tint on the floor near the wall change over from the old colour to the new one. Drag Hysteresis down and it changes faster, but a probe also jumps more from one update to the next. Drag Probes a frame up and every probe is visited sooner, at the cost of drawing more cubes each frame.

_paint.baseColor.setFrom(_colours[wallColour]);
_field
  ..gpuUpdates = probesPerFrame.round()
  ..hysteresis = hysteresis;

Note. The updates need cube textures and a second colour attachment. On a device without them, the field stays what the application filled it with, which for this page is black. RenderSettings.frameWorkBudget can hold the updates to a time budget per frame; a probe that does not fit waits for the next frame.