#One fluid. Every spell.
The effects don't draw sprites. They push dye, glow, heat and velocity into a 64 × 96 × 64 grid (on high), and the solver does the rest. A portal's swirl field pulls a burst of dye into its eye. Smoke from a lamp parts around a force field. Rings fired from a cannon roll through whatever is already in the air.
setIntensity() eased from 0 to 1 over five seconds on a cleared field. The swirl field winds the rim dye into the eye while sparks spiral inward.- presets, one file each
- 7
- effect types, plus impulse()
- 8
- quality tiers, 96 K to 768 K voxels
- 4
- high on Apple M5 at 1280×720 (display cap)
- 120 fps
#What's in the box
- Composable effects
addEffect()returns a handle you can move, re-aim, recolour, fade or remove while it runs. Up to 8 emitters, 4 impulses, 4 rings and 2 swirl fields share one frame. - A real 3D fluid
Velocity, temperature, fuel and RGB dye plus glow, advected with limited MacCormack, vorticity confinement and a Jacobi pressure solve.
- Vortex rings that hold together
A local confinement boost around each ring core, plus an analytic rolling torus, keeps the hole open for the full 4.5 s life.
- Coloured combustion
Fuel burns into palette-ramped flame with tinted soot, embers and heat haze. The dragon breathes green by default.
- Effects that light the scene
An async GPU reduction drives a shadow-casting point light from the fluid's emission. Spell orbs each carry their own light.
- Depth-aware compositing
The volume is raymarched against your scene's depth, accumulated over frames, reflected on glossy floors and graded with a hue-preserving tone map.
#Seven presets, ready to load
Each preset is a plain params object plus a list of effects, in src/config/presets/. Load one with loadPreset(), or print the live system with getParams() and paste it back.
portalA rune ring with a spinning vortex that pulls violet and cyan dye into its eye.genieLampGlowing blue smoke from a spout, wound into a column, puffing a ring every 3.6 s.spellOrbsThree orbs on tilted orbits trailing glow, each carrying a point light.vortexRingsA cannon firing tight, rolling smoke rings across the courtyard.dragonBreathPulsing jets of green, palette-ramped fire with embers and heat haze.forceFieldThree coloured vents whose plumes part around a wandering energy sphere.#A few lines to a spell
import * as THREE from 'three';
import { MagicSystem } from 'naturegl-magic';
// your renderer, scene and camera, as usual
const magic = await MagicSystem.create({ renderer, scene, camera, preset: 'portal' });
magic.addEmitter({ position: [1.3, 0.3, 0.8], velocity: [0, 1.6, 0], color: '#ff8a3d', rate: 12, glow: 6 });
const timer = new THREE.Timer();
renderer.setAnimationLoop(t => {
timer.update(t);
magic.update(timer.getDelta()); // effects, fluid, sparks, glow light (offscreen)
magic.render(); // replaces renderer.render(scene, camera)
});#Smoke rings that survive the trip
vortexRings preset: one ring every 1.05 s at 2.1 m/s, slowing with drag: 0.42. Each ring rolls around its core and fades as it reaches the open faces of the simulation box instead of being clipped.#Part of the NatureGL series
NatureGL Magic follows the same contract as the other libraries in the series: it renders into a scene and camera you own, and all of its offscreen work happens in update(dt).