Alpha modes
Open the live demo · Read the source · View on GitHub
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.
blendhas to draw after everything opaque and be sorted by distance, which costs a sort every frame.hashedis 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;
}