Mesh particles
Open the live demo · Read the source · View on GitHub
A billboard is a quad that always faces the camera, which is right for a
spark or a puff of smoke and wrong for a shard of debris that should tumble
and show its edges. MeshParticleContributor draws every live particle as a
copy of a real mesh instead, in one instanced call.
Step 1: One mesh, uploaded once #
The shape is ordinary geometry, built and uploaded exactly the way a wall or a prop would be. Nothing about it knows it is about to become a particle.
// One shape, uploaded once. Every particle is a placement of it rather
// than a separate draw, which is what keeps this a single instanced call
// however many shards are on screen.
final DeviceMesh shard = DeviceMesh.upload(
context.device,
CuboidShape(size: Vector3(0.12, 0.12, 0.12)).build(),
);
Step 2: A second contributor, not a second mode #
ParticleContributor and MeshParticleContributor share a pool and a pass
and almost nothing else: their vertex buffers, their layouts and their
shaders all differ, because a billboard is four vertices built on the CPU
and a mesh particle is eight floats of placement next to geometry that is
already on the device.
_contributor = context.renderer.addContributor(
MeshParticleContributor(_particles, mesh: shard),
);
Step 3: What the frame should show #
The claim is the ordinary one for a particle page: the contributor is active, the system holds exactly as many particles as were burst, and the frame actually drew something.
if (!_contributor.isActive) {
throw StateError('the mesh particles should be alive and drawing');
}
if (_particles.aliveCount != _count || frame.drawCalls < 1) {
throw StateError('the shards did not reach the frame');
}
Note. One mesh per contributor. Two different shapes of debris in the same burst are two contributors, which is cheap to arrange and honest about the extra draw call it costs.