8 GLSL Shader Systems in 6 Months: Building Elemental Effects for a Character Animation Engine
Source: Dev.to
What This Is
Emotive Engine is an open‑source project.
For v3.4.1 I wrote 8 elemental shader systems from scratch: fire, water, ice, …
The point was to prove that the architecture repeats – every element follows the same pattern.
Live Demo
[All 161 gestures]
Element Architecture
Every element type in the system consists of exactly 5 components:
| Component | Description |
|---|---|
Factory (fireEffectFactory.js) | Takes a config object and returns a gesture with a 3d.evaluate() function that drives transforms per frame. |
Instanced Material (InstancedFireMaterial.js) | A THREE.ShaderMaterial with custom GLSL. GPU‑instanced so all elements of one type render in a single draw call. |
Overlay Shader (FireMaterial.js) | Paints directly onto the mascot mesh. Uses the same visual language as the instanced material but is mapped to the character’s geometry. |
| Gesture Configs (one file per gesture) | Declarative objects: duration, spawn mode, model list, cutout pattern, grain, atmospheric preset. |
Registration (ElementRegistrations.js) | Wires everything into the ElementTypeRegistry: models, materials, update hooks, bloom thresholds. |
Example: Fire Registration
ElementTypeRegistry.register('fire', {
basePath: 'models/Elements/Fire/',
models: [
'flame-wisp.glb',
'flame-tongue.glb',
'ember-cluster.glb',
'fire-burst.glb',
'flame-ring.glb',
],
createMaterial: createInstancedFireMaterial,
updateMaterial: updateInstancedFireMaterial,
setShaderAnimation: setInstancedFireArcAnimation,
resetShaderAnimation: resetFireAnimation,
setGestureGlow: setInstancedFireGestureGlow,
setCutout: setInstancedFireCutout,
resetCutout: resetFireCutout,
setGrain: setFireGrain,
resetGrain: resetFireGrain,
// … more hooks
});Note: Every element has this exact same shape. The
ElementInstancedSpawnernever callsregistry.createMaterial()orregistry.updateMaterial()directly; the element‑specific GLSL handles the rest.
Fire Shader Details
Naïve Additive Approach
// color = noise, alpha = noiseProblem: The fire looks muddy because color and alpha are coupled.
Fixed Approach – Decouple Color from Alpha
// Color ramp always in warm‑to‑hot range (0.5‑1.0)
// Noise shifts hue (orange → yellow‑white) but color is NEVER dark/brown.
float colorIntensity = 0.5 + localIntensity * 0.5;
vec3 color = fireColor(colorIntensity, uTemperature, edgeFactor);
// Alpha drives visibility with a wide smoothstep
float alpha = smoothstep(0.1, 0.85, localIntensity) * uOpacity;
// No floor color needed. Low‑noise areas are invisible via alpha,
// not dark via color.
if (alpha License
MIT licensed – PRs welcome.
- npm:
npm install @joshtol/emotive-engine - CodePen collection: Emotive Engine — Elemental Shaders