Particle FX Demo
App: apps/particle_fx_demo/
Demonstrates the two new non-physics sprite render modes on
ParticleEmitter (engine
main, ADR 0102) — the render path that backs water splashes and ripples. Both modes
reuse the existing sprite pipeline, so there is no Vulkan renderer change; a sprite
particle is a pinned kinematic decal (no Jolt body) that stays where it spawns and
animates only via a scale-over-life and an alpha-over-life curve.
- SpriteGround (mode 1) — a soft ring texture laid flat on the world XZ plane (a new
BillboardMode::Ground). Bursting one ring per beat at a fixed site gives concentric, expanding, fading rings — exactly a raindrop, footfall, or floating object hitting water. - SpriteBillboard (mode 2) — the same ring, camera-facing, as a twinkling sparkle above the surface.
The default mode, PhysicsMesh (mode 0), is unchanged: a Jolt body plus an instanced mesh (splash spray still uses this mode). See Physics Particles Debris for the physics-body path.
Run from root
Section titled “Run from root”% ./plume3d particle_fx_demoA dark, water-like ground plane sits at y = 0; the pale rings read against it. The camera
is raised and pulled back looking down at the surface, so the flat ground rings read as
ellipses and the camera-facing sparkles clearly stand up off the plane. It self-screenshots
(particle_fx.png) at a fixed frame, then exits.
What it does
Section titled “What it does”- Draws a dark ground plane immediate-mode, then calls
_scene.draw()— which submits the particle sprites through the sprite pipeline. - Creates a
SpriteGroundripple emitter and aSpriteBillboardsparkle emitter viaScene.createParticleEmitter. - Loads a soft ring texture (
textures/ring.png) withTexture.loadand gives it to both emitters viasetTexture. - Each beat, moves the ripple emitter to the next of three fixed sites and
burst(1)s a single ring, so concentric rings accumulate at each site. - The sparkle emitter runs continuously at its emission rate.
The two emitters
Section titled “The two emitters”The ripple emitter is pinned flat on the surface; it grows from a point to a wide ring while fading out. The sparkle emitter faces the camera and shrinks to a warm twinkle:
// Surface-pinned ripple rings (SpriteGround) — flat on the water, expand + fade.// Args: name, maxBodies, size, mass, lifetime, emissionRate, x, y, z_ripples = _scene.createParticleEmitter("ripples", 64, 0.1, 0.0, 1.8, 0.0, 0.0, 0.02, 0.0)_ripples.setRenderMode(1) // SpriteGround_ripples.setTexture(_ring) // a soft ring texture_ripples.setScaleCurve(0.25, 3.2) // half-size: grows from a point to a wide ring_ripples.setAlphaCurve(0.9, 0.0) // fades out as it grows_ripples.setColor(0.72, 0.90, 1.0) // pale cyan tint_ripples.play()
// Camera-facing sparkle (SpriteBillboard) — warm specks twinkling above the surface._sparkle = _scene.createParticleEmitter("sparkle", 96, 0.05, 0.0, 0.8, 30.0, 0.0, 0.35, 0.0)_sparkle.setRenderMode(2) // SpriteBillboard_sparkle.setTexture(_ring)_sparkle.setScaleCurve(0.22, 0.03) // shrinks to a twinkle_sparkle.setAlphaCurve(1.0, 0.0)_sparkle.setColor(1.0, 0.95, 0.72) // warm white_sparkle.play()Bursting one ring per beat
Section titled “Bursting one ring per beat”A sprite particle is pinned where it spawns, so moving the emitter and bursting a single particle each beat drops a fresh ring at that spot:
// In update(dt), on each beat:var s = sites[_site % sites.count] // three fixed (x, z) ripple sites_site = _site + 1_ripples.setPosition(s[0], 0.02, s[1])_ripples.burst(1)Drawing
Section titled “Drawing”The ground is drawn immediate-mode; Scene.draw() submits the emitter quads through the
sprite pipeline:
Graphics.drawMesh(_ground, _shader, model) // immediate-mode ground_scene.draw() // submits the particle sprites- ParticleEmitter — sprite render modes
—
setRenderMode,setScaleCurve,setAlphaCurve,setColor,setTexture(pluscreateParticleEmitter,play,setPosition,burst). - Texture —
Texture.load(path)for the soft ring sprite. - Scene —
createParticleEmitter,draw. - Graphics — immediate-mode
drawMesh,setLights.