NatureGL Magicv1.0.0

More

Troubleshooting

The common problems, what causes them, and the fix for each.

Nothing appears

Check three things. The effect must be inside the simulation box: by default x and z from −2 to 2 and y from 0 to 6 m. You must call magic.update(dt) every frame. And you must call magic.render() instead of renderer.render(scene, camera), because a plain render never draws the volume.

"MagicSystem: WebGL2 + EXT_color_buffer_float required"

The renderer is WebGL1, or the GPU can't render to float textures. Every current desktop and mobile browser supports both. In headless Chromium, launch with a real GPU (--use-angle=metal --enable-gpu --ignore-gpu-blocklist on macOS), as scripts/smoke.mjs does.

The effect stops at an invisible wall

It reached the edge of the box. Move or enlarge the box with domain: { min, width } (the height is 1.5 × width). Density already fades out over render.edgeFade metres toward the open sides and top. A larger box keeps the same cell count, so its detail is coarser.

"more than 8 emitters active; extra ones are ignored this frame"

All effects share fixed slots: 8 emitters, 4 impulses, 4 rings, 2 swirls and 4 spark spawners per frame, plus one obstacle and one container. Spell orbs use one emitter each and a portal uses two. See per-frame limits.

The picture looks washed out, or exposure settings do nothing

render() output is already tone-mapped (ACES) and sRGB-encoded. If you run it through another pass that tone-maps or encodes, such as an OutputPass, it's done twice. renderer.toneMapping and toneMappingExposure don't affect the result: set params.post.exposure instead. With composite(), pass a linear HDR colour texture, not an already tone-mapped one.

The magic doesn't reflect on my floor

Reflections need three things: the material patched with patchReflectiveFloor(), the surface at the domain floor height (domain.min.y), and a low enough roughness. The mask is 1 − strength · (1 − roughness · 1.25), so a roughness of 0.8 or more doesn't reflect at all. render.reflection scales the result.

Everything reflects, including the background

With composite(), the colour's alpha is read as the reflection mask. Clear your target with alpha 1 (renderer.setClearColor(0x000000, 1)) and make sure your materials write alpha 1, except the reflective ones.

The glow light doesn't cast shadows

Enable renderer.shadowMap.enabled = true, and set castShadow and receiveShadow on your meshes. The light itself casts by default (glowShadows: true).

Vortex rings come out soft, or break up

Raise the quality tier: the ring core is only a few cells wide on low. Keep physics.turbulence low (the vortexRings preset uses 0.25), since noise forcing tears the rings apart. Raise torus.sharpness for a crisper analytic edge.

The dragon's breath barely catches fire

Combustion needs physics.burnRate of about 5 and low dissipation. Load the dragonBreath preset, or copy its physics. See Dragon breath.

Two dragons have the same colour

The flame palette is global to the solver: the last dragonBreath updated sets it for all. Dye colours can still differ.

The simulation runs away and fills with noise

physics.vorticity is too high. Keep it about 1–4, because curl is measured per cell. If you raised velocitySharpness, lower vorticity to match.

The force field wanders away from where I put it

Its idle path is centred on wander.center (default [0, 2.4, 0]), not on position. Pass wander: { center: [...] } or wander: { enabled: false }.

The fluid resets when I change quality

That's expected. setQualityLevel() rebuilds the solver at the new resolution, so the fields start empty. Change tiers early, or while nothing is on screen.

#Known limitations

  • One simulation box per MagicSystem. Each system runs its own screen pipeline, so stacking several systems in one view isn't supported yet.
  • Per frame: 8 emitters, 4 impulses, 4 rings, 2 swirls, 4 spark spawners, 1 obstacle sphere and 1 container. Extra effects are ignored, with one console.warn per slot type.
  • One flame palette per system.
  • render() owns the final tone mapping (ACES + sRGB). For your own post chain, render to a target and treat the output as display-referred.
  • Volume reflections work only on surfaces at the domain floor height whose material writes alpha < 1.
  • WebGL2 with EXT_color_buffer_float only; there's no WebGPU/TSL path.