Spell VFX fields
Same fire bolt, same seed on both halves.
Left: GPU particles only.
Right: a curl-noise field bends every path, plus bloom and heat distortion.
Then one template becomes ice by swapping parameters.
The problem
Flipbook spell effects look the same on every cast, and changing one means re-authoring art. @Ian_cadle's open-source ultra-elements library takes the other route: 39 three.js spells built from GPU particles, noise fields, volumes and post, with every value on a live slider.
How it works
- One GPU particle pool for every spell. State lives in three RGBA32F textures (position + age, velocity + life, kind + seed), and one fragment-shader pass updates all of it each frame. On the High tier that's 512 × 512 = 262,144 slots.
- Behaviour is data. Each particle "kind" is a row in a table texture: drag, gravity, buoyancy, curl, lifetime, size, temperature or colour. Moving a slider rewrites texels, so nothing recompiles.
- The noise field. At load, the library bakes a 64³ curl-noise texture. Curl noise is divergence-free, so smoke swirls instead of bunching up or thinning out. Each particle adds curl(position × scale) × strength to its acceleration.
- Soft particles like flame and smoke draw into a half-resolution pass with weighted blended transparency, so nothing has to be sorted. It also writes the heat-distortion offset. Sparks are additive at full resolution.
- Fire colour is a blackbody temperature: 2400 K cooling to 1100 K.
What I hit
In headless Chrome with software GL, the whole frame came out flat purple. The FX composite has a "void collapse" negative-flash effect behind if (uInvert.w > 0.0). With the value at 0, the software renderer still applied it. I disabled that one branch, since no spell here uses it.
At curl strength 2, the value the repo's particle sandbox uses for its flame, my short-lived bolt flames barely changed shape. I went to 10, and raised the noise scale from 0.35 to 0.9, so the field repeats about every 1.1 m and the swirls fit the bolt.
My build
three.js 0.186.1 with the library at commit 698b720. Its particle pool, noise bake, FX pass and post run unchanged apart from that branch. My part is a stone platform, a training dummy and one projectile template: an emitter at the bolt head, trail emitters, and an impact burst of 1,200 sparks, 260 flame puffs and 40 chips. Fire and ice are two parameter sets for the same template. I didn't use the library's finished spells, which need its full arena engine, only the building blocks under them.
Trade-offs
- Every look is parameters, no texture sequences
- One particle pool and one FX pass serve every spell
- Needs WebGL2 with float render targets
- The half-res FX pass can halo at silhouettes
- The author measured Fireball at 3.07 ms for the worst frames on an RTX 3060 at 3.3 MP. My renders are software, so no timing of my own.
When to use it
Games that need many elemental variants from one system, tuned live instead of re-exported.
Source and credits
Topic credit: @Ian_cadle, open-source three.js elemental spell library (post: https://x.com/Ian_cadle/status/2116288926608499067)
Repo: https://github.com/Humpalumps/ultra-elements (MIT license), branch main, commit 698b720c81d8f1f282bae53633d72413b5c4408d
Code: reused unchanged from the repo: src/vfx/Particles.js (GPU particle pool and kind table), src/vfx/noise.js (baked curl-noise and blackbody), src/vfx/FxTarget.js (half-res transparency + distortion pass, with one branch disabled for software rendering), src/engine/PostFX.js (bloom, tone mapping), src/vfx/common.js and src/engine/fullscreen.js / quality.js. Read for structure: src/spells/cast.js, src/spells/_template.js, dev/particles.js. The platform, training dummy, projectile template and its fire and ice parameter sets are my own. Everything in the video was rendered by me; no footage or screenshots from the original post. No external assets.