flutter3d
Showcase Changelog 38 packages API reference

Alpha modes

since 0.7.0 Shading and materials

A material can be see-through, and there is more than one way to decide what that means. MaterialAlphaMode has four answers: opaque, mask, blend and hashed. This page draws the same soft disc four times, one per mode, in front of a blue wall, from left to right in that order.

Step 1: A soft disc #

The texture is white with an alpha that is full at the centre and fades to nothing at the rim. Every mode reads that alpha, multiplied by the alpha of the material's baseColor, and only what they do with it differs.

/// White everywhere, with an alpha that is one at the centre and falls to
/// zero at the rim: a soft disc.
ByteData _softDisc() {
  final Uint8List bytes = Uint8List(_size * _size * 4);
  for (var y = 0; y < _size; y++) {
    for (var x = 0; x < _size; x++) {
      final double dx = (x + 0.5) / _size * 2.0 - 1.0;
      final double dy = (y + 0.5) / _size * 2.0 - 1.0;
      final double alpha = (1.0 - math.sqrt(dx * dx + dy * dy)).clamp(0, 1);
      final int at = (y * _size + x) * 4;
      bytes[at] = 255;
      bytes[at + 1] = 255;
      bytes[at + 2] = 255;
      bytes[at + 3] = (alpha * 255).round();
    }
  }
  return bytes.buffer.asByteData();
}

Step 2: Four materials, one for each mode #

The four materials are identical except for alphaMode. opaque ignores alpha altogether, so the leftmost panel stays a solid square. mask keeps a pixel or drops it against alphaCutoff. blend mixes the panel with what is behind it. hashed keeps a share of the pixels equal to the opacity, chosen by a noise pattern fixed to world position.

_materials = <Material>[
  for (final MaterialAlphaMode mode in MaterialAlphaMode.values)
    Material(
      name: mode.name,
      baseColor: Vector4(0.95, 0.55, 0.2, opacity),
      albedo: disc,
      albedoSampler: SamplerOptions.linearClamp,
      alphaMode: mode,
    ),
];

Step 3: Stand them up #

The panels are flat planes turned to face the camera and laid out in a row. The blue wall behind them is what makes blending and cutting visible.

final DeviceMesh panel = DeviceMesh.upload(
  context.device,
  const PlaneShape(width: 1.4, depth: 1.4).build(),
);
final Quaternion upright = Quaternion.axisAngle(
  Vector3(1.0, 0.0, 0.0),
  math.pi / 2,
);
final Scene scene = Scene();
for (var i = 0; i < _materials.length; i++) {
  scene.add(
    MeshNode(panel, _materials[i], name: _materials[i].name)
      ..setRotation(upright)
      ..setPosition((i - 1.5) * 1.6, 0.0, 0.0),
  );
}

Note. blend has to draw after everything opaque and be sorted by distance, which costs a sort every frame. hashed is drawn with the opaque surfaces and needs no sort, at the price of visible noise.

Step 4: Move the numbers #

Drag Alpha cutoff and the second panel's edge moves in and out: it is a hard circle, because a pixel is either above the cutoff or not. Drag Opacity and watch the fourth panel: its pixels thin out, and from a distance they average to that opacity.

Turn on Double sided and drag the view round to the back of the panels. With it off, the back of a surface is culled and you see straight through; with it on, both faces draw.

for (final Material material in _materials) {
  material
    ..baseColor.w = opacity
    ..alphaCutoff = cutoff
    ..doubleSided = doubleSided;
}