Create a 3D GPU particle effect
A working C# burst: materials, geometry, triggering and visibility diagnosis.
A GPU particle effect needs three separate pieces: an emitter (GpuParticles3D in C#), a material that simulates particle behavior, and geometry/materials that draw each particle. An emitting checkbox alone does not create visible geometry.
Create the effect in the editor
- Add a GPUParticles3D Entity to the 3D scene.
- Under Process Material, create a
ParticleProcessMaterial. Start with a clear Direction, modest initial velocity and deliberate Gravity; do not tune several forces at once. - Under Draw Passes, assign a Mesh to Pass 1, for example a small sphere for a functional test. Assign that mesh a visible surface material.
- Put the emitter in front of the active Camera3D. For a lit surface material, provide suitable lighting; for a visibility test, an unshaded surface removes that variable.
- Set a Visibility AABB that covers the whole effect, including moving particles and mesh size. This is a culling bound, not a collision box.
- Enable emission for a continuous effect, or enable One Shot and trigger a restart for a burst.
Check the active renderer and target device. The example below is executed in Forward+/Vulkan on Windows; it is not proof of every GPU/backend's behavior. Choosing GPU particles does not guarantee a performance improvement for every small effect.
Run a complete C# burst
Save this helper in your C# project. It returns a configured emitter without adding it to the tree:
using Reckfell;
public static class ParticleBurst
{
// A functional burst. Replace its geometry/material with your effect assets.
// Add to the tree, then call Restart() to trigger it.
public static GpuParticles3D Create()
{
var movement = new ParticleProcessMaterial
{
Direction = Vector3.Up,
Spread = 25f,
Gravity = Vector3.Zero,
InitialVelocityMin = 0.6f,
InitialVelocityMax = 0.6f,
};
var surface = new StandardMaterial3D
{
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
AlbedoColor = new Color(1f, 0.4f, 0.1f),
};
var mesh = new SphereMesh { Radius = 0.06f, Height = 0.12f, Material = surface };
return new GpuParticles3D
{
Emitting = false,
OneShot = true,
Amount = 24,
Lifetime = 0.75,
Explosiveness = 1f,
LocalCoords = true,
UseFixedSeed = true,
Seed = 42,
ProcessMaterial = movement,
DrawPass1 = mesh,
VisibilityAabb = new Aabb(new Vector3(-2, -2, -2), new Vector3(4, 4, 4)),
};
}
}From your existing Entity, call var burst = ParticleBurst.Create();, then AddChild(burst); and burst.Restart();. Place that Entity where the effect should begin. The example starts emission explicitly, uses a repeatable seed and produces a short orange burst. Its spheres are functional geometry, not finished smoke, vegetation or a photorealistic effect. Replace the draw asset/material for your actual art direction.
The process material controls motion; the sphere's surface material controls its visible color/shading. Changing the process material is not a substitute for assigning a draw mesh. Conversely, a draw mesh does not supply gravity or velocity simulation.
Stop, pause and retrigger are different
| Action | Effect |
|---|---|
Emitting = false | Stop creating new particles. Existing particles can remain visible until they expire. |
SpeedScale = 0 | Pause ordinary simulation progression; existing geometry can still be drawn. Moving a local-space emitter can still move its visible particles. |
Restart() | Clear/restart the cycle and enable emission. Suitable for explicitly retriggering a burst, including before the previous burst finished. |
Wait for Finished | Observe completion of a one-shot cycle. It is not a completion signal for every continuous emitter stopped manually. |
For an explosive one-shot, Emitting can already be false while particles remain alive. Do not use it as “nothing visible remains”. Connect Finished before triggering when completion matters. Repeatedly assigning Emitting true during an active one-shot is not equivalent to Restart and can cancel its pending completion notification in this implementation.
UseFixedSeed = true preserves the chosen Seed across the example's restart overloads. Restart(keepSeed: true) also preserves the current seed. A seed alone is not a promise of identical simulation across all drivers, time steps or engine builds.
1.0.0 timing limitation: after a long SpeedScale = 0 pause and resuming to 1, the tested one-shot can emit Finished while GPU particles are still visible. The scene timer multiplies accumulated elapsed time by the current speed scale; it is not a GPU-expiry fence. Do not recycle a time-scaled effect from this signal alone. Keeping a simple one-shot at fixed speed avoids this particular pause/resume case; validate a separate lifecycle policy when changing simulation time.
Choose the coordinate space
With LocalCoords = true, existing particles follow the emitter transform. Use it for an effect attached to a moving object. With false, spawned particles occupy world space while the emitter can move on, useful for a trail left behind. Configure the intended space before starting; changing it on a running effect is not an automatic visual-space conversion workflow.
The local GPU test pauses simulation, moves the emitter, and confirms both cases from rendered pixels. A paused particle system is therefore not necessarily a screen-frozen image when its emitter or camera moves.
Diagnose invisible particles
| Symptom | Check first |
|---|---|
| No particles ever appear | ProcessMaterial, Draw Pass 1, mesh size, camera/layers, update timing and emission trigger. |
| Simulation runs but nothing draws | The draw mesh and its surface material; removing DrawPass1 hides the tested effect without being a simulation stop command. |
| Effect disappears near the edge of view | VisibilityAabb must cover its full motion envelope; an offscreen bound can cull particles that would otherwise be onscreen. |
| Smoke looks like opaque cards | Surface transparency and texture alpha; process material is not the drawing material. |
| Process color does not affect the mesh | Check that the surface material uses particle vertex color, for example Vertex Color → Use As Albedo in the standard material. |
| Burst will not retrigger as expected | Use Restart explicitly; distinguish an inactive cycle from an Emitting getter during a live cycle. |
| A moving emitter drags an intended trail | Check LocalCoords. |
For an estimated bound, use the editor's generation tool or CaptureAabb() while the intended motion is actually running, then validate worst-case travel. A current-frame capture is not a bound for every future velocity, spawn shape or trail. Do not use an enormous AABB as a permanent fix: it prevents useful culling.
CPU particles use a different simulation path and a different feature set. Treat conversion as a separate effect-validation task; a successful conversion does not prove identical visuals, timing or performance. Advanced trails, collision, turbulence and sub-emitters have their own setup requirements.
The native evidence covers actual GPU pixels, burst completion/restart, emission stop, pause, coordinate spaces, bounds and draw-pass presence. No beauty illustration, CPU conversion, advanced particle features, performance budget or full export/device matrix is claimed here. See emitter properties for timing and cost controls.