Textured billboards
Open the live demo · Read the source · View on GitHub
An untextured particle draws as a procedural disc: cheap, and the same shape
every time. Handing ParticleContributor a texture swaps in a sprite,
sampled per particle, and the sprite can be anything a texture can hold.
Step 1: Paint one sprite #
This one is drawn on the CPU, a spark shaped like a four-pointed star rather than a circle: bright along its two axes, dark at the corners.
/// A four-pointed spark: bright along the axes, dark at the corners. Not a
/// shape the procedural disc every untextured particle uses can make.
ByteData _sparkSprite() {
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 along = math.max(dx.abs(), dy.abs());
final double across = math.min(dx.abs(), dy.abs());
final double alpha = (1.0 - along) * (1.0 - across * 3.0);
final int at = (y * _size + x) * 4;
bytes[at] = 255;
bytes[at + 1] = 220;
bytes[at + 2] = 160;
bytes[at + 3] = (alpha.clamp(0.0, 1.0) * 255).round();
}
}
return bytes.buffer.asByteData();
}
Step 2: Put it on the device #
final TextureHandle sprite = context.device.createTextureFromPixels(
width: _size,
height: _size,
format: TextureFormat.r8g8b8a8UNormInt,
pixels: _sparkSprite(),
)!;
Step 3: Hand it to the contributor #
null picks the procedural disc; anything else picks the textured stage.
The two are different fragment shaders, not the same one with a white
default, because binding a texture to a slot a compiled shader has no room
for is expensive to get wrong.
_contributor = context.renderer.addContributor(
ParticleContributor(_particles, texture: sprite),
);
Step 4: What changed #
The page's own check is only that a texture is actually there: the shape a sprite draws is a matter for your eyes, not for a test running headless.
if (_contributor.texture == null) {
throw StateError('the contributor should be drawing a sprite');
}
Warning. Build a texture with a mip chain if you can, and check
GraphicsDevice.supportsMipmapsfirst. A particle is a quad that shrinks as it recedes, and one without a chain sparkles on the way out.