◈ Rollshade Open the tool

Using Rollshade effects in three.js

Rollshade gives three.js games ready-made moves, status effects and placed effects. The easiest way to use them is the npm package rollshade (part 1). The tool can also export effects as files and generate looping TSL shaders you edit as node graphs (part 2). Everything runs on WebGPURenderer, which falls back to WebGL2.

Part 1 · The rollshade package

Install

npm i rollshade [email protected]

The package is the same runtime the tool uses, under the MIT License. It needs three r186 and a WebGPURenderer from three/webgpu, which falls back to WebGL2 on its own. Try everything in the demo game first if you like.

import * as THREE from 'three/webgpu';
import { FXSystem, effect } from 'rollshade';

const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
await renderer.init();

const fx = new FXSystem({ scene, camera, renderer, post: true, feel: true });
fx.add(effect('meteor', 'fire'));
await fx.prewarm();

const timer = new THREE.Timer();
renderer.setAnimationLoop((ms) => {
  timer.update(ms);
  fx.update(Math.min(timer.getDelta(), 0.05));
  fx.render();
});

Moves

A move is a complete attack, heal or warp: charge-up, travel, impact, lights and smoke. effect(recipe, element) makes its definition; fx.play() fires it and returns a handle whose hit events tell your game when to apply damage.

fx.play('fire-meteor', { from: hero.hand, to: enemy }).on('hit', (e) => enemy.damage(10 * e.power));

Status effects

Put a status on any mesh or animated character. Its textures stay, the pattern sticks to the surface while it animates, and the original materials come back when the status is gone.

await fx.prewarmStatus(enemyModel);                 // once per kind of model
const frozen = fx.status(enemy, 'freeze', { duration: 1.2 });
frozen.on('full', () => (mixer.timeScale = 0));    // stopping the animation is up to you
frozen.stop();                                     // thaw; the ice shatters

Burn, freeze, shock, poison, petrify, dissolve, appear, bless, curse, shield and stun. progress (0–1) drives how far freeze, petrify and dissolve have spread; a shield ripples where you call impact(point). Statuses last until you stop them, except appear. Browse them in the Status effects group of the Motion mode.

Placed effects

fx.loop('torch', new THREE.Vector3(2, 0, -3));
fx.loop('portal', gate.position, { rotation: Math.PI / 2, element: 'dark' });
const aura = fx.loop('aura', hero);          // follows the character
aura.stop();

Torch, campfire, candles, portal, magic circle, save point, barrier dome, aura and beacon. They come with simple low-poly stands; pass prop: false to put the effect on your own model. Loops share a fixed pool of point lights (loopLights, 4 by default) because changing the number of lights makes three.js recompile every material.

React Three Fiber

import { FX, useFX, Status, Loop } from 'rollshade/react';

<Canvas gl={async (props) => { const r = new THREE.WebGPURenderer(props); await r.init(); return r; }}>
  <FX effects={[effect('meteor', 'fire')]} feel>
    <Status target={enemyRef} name="burn" active={burning} />
    <Loop name="torch" position={[2, 0, -3]} />
  </FX>
</Canvas>

useFX() returns the system once it has prewarmed, for fx.play(). Needs React Three Fiber 9 or later.

With AI coding assistants

The package ships an agent skill at node_modules/rollshade/skills/rollshade/SKILL.md; install it for Claude Code, Cursor, Codex and others with npx skills add ./node_modules/rollshade/skills/rollshade. A compact reference for language models is at /llms.txt and the full one at /llms-full.txt. Unknown names throw errors that list the valid ones, so an assistant can correct itself.

Performance

Part 2 · Exporting from the tool and the TSL shader lab

The Motion mode exports the same runtime and definitions as files (a ZIP with an example, sprite sheets for 2D games, or video), for status effects and placed effects too. The Shader modes generate a single ES module that exports createEffect: TSL node materials, the meshes the effect needs, and a render pipeline with bloom on the glowing parts only.

Set up the renderer

npm install three

Save the code as a file next to your script (for example with Download), then:

import * as THREE from 'three/webgpu';
import { createEffect } from './rollshade_fire_k3x9ab.js';

const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.append(renderer.domElement);
await renderer.init();

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, innerWidth / innerHeight, 0.05, 100);
camera.position.set(0, 1.6, 6);
camera.lookAt(0, 0.75, 0);

Import everything from three/webgpu, not from three. Loading both copies in one page breaks node materials.

Runtime format (default)

The Runtime tab of the code panel writes the same effect for the shared runtime rollshade-fx.js (Motion effects). Helpers are imported from the runtime and the module exports defineLoop({ id, period, bloom, create }). Add it with fx.add() next to Motion effects and place it with fx.spawn(id, { at }) (or fx.spawn(id, { object: mesh }) for effects made on a model). One fx.render() draws everything with one bloom, and helpers are never duplicated however many effects you place. The handle has uniforms, pause(), resume(), moveTo() and stop(). The rest of this section describes the Single file format, a self-contained module as before.

A standalone effect

const fx = createEffect({ scene, camera, renderer });

renderer.setAnimationLoop((ms) => {
  fx.update(ms / 1000);
  fx.renderPipeline.render();
});

The effect stands on the ground at the origin and is about two units tall. fx.object is the group that holds its meshes and particles, so you can move, rotate or scale it like any other object.

An effect on a model

Effects made with On a model take the object to decorate:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const gltf = await new GLTFLoader().loadAsync('hero.glb');
scene.add(gltf.scene);
scene.environment = myEnvironmentMap;

const fx = createEffect({ object: gltf.scene, scene, camera, renderer });

Time and the loop

fx.update(seconds) sets the effect's phase to (seconds / period) % 1. Every moving part repeats exactly once per period, so any clock works and the effect always loops seamlessly. The period defaults to the loop length you picked in Rollshade; pass period to createEffect to change it. To scrub or pause, set fx.uniforms.phase.value (0 to 1) yourself instead of calling update.

Motion effects

Effects from the Motion mode are built differently from the other modes. Instead of one file that holds everything, you get two kinds of files:

Download zip packs the runtime, its types, the chosen effects, an fx/index.js that imports them all and a runnable example.html. Add effects to the Export list to download several at once.

import * as THREE from 'three/webgpu';
import { FXSystem } from './rollshade-fx.js';
import effects from './fx/index.js';

const fx = new FXSystem({ scene, camera, renderer, post: true, feel: true });
fx.add(...effects);
await fx.prewarm();

const h = fx.play('fire-projectile-k3', { from: hero.hand, to: enemy });
h.on('hit', (e) => enemy.damage(10 * e.power));

const timer = new THREE.Timer();
renderer.setAnimationLoop((ms) => {
  timer.update(ms);
  fx.update(timer.getDelta());
  fx.render();
});

Sprite sheets for 2D games

Sprite sheet… plays the effect once and lays every frame out in one PNG. The dummies, the sword and the floor are left out; only the effect remains.

Bloom and your render loop

With Bloom ticked under Include, the module creates a RenderPipeline that renders the scene and adds bloom only to the emissive parts, so the rest of your scene does not glow. Call fx.renderPipeline.render() instead of renderer.render(). The pipeline keeps transparency, so it also works over a transparent canvas.

If you already have your own post-processing, untick Bloom. The module then returns no pipeline, and you render as usual with renderer.render(scene, camera) or your own pipeline. The effect still writes its glow to the emissive output, so a bloom pass that reads emissive picks it up.

Adjusting the effect at runtime

fx.uniforms holds the sliders from the Layers panel, named after the layer:

fx.uniforms.flame1Intensity.value = 2;
fx.uniforms.flame1Color.value.set('#ff8800');
fx.uniforms.bloomStrength.value = 0.8;

Changing a uniform does not rebuild any shader, so it is cheap to animate every frame.

Removing the effect

fx.dispose();

dispose puts the original materials back on the model, removes the meshes and particles the effect added and frees its GPU resources.

Models without UVs

Some patterns can follow a mesh's UV layout. With Coordinates set to Auto, the code checks each mesh and uses its UVs when it has them and its positions when it does not, so models without UVs still work. If you know your models, choose UV only or Position only under Include to remove that branch from the code. Most patterns use positions in any case, so they stay attached to the surface and need no UVs.

What to include

The preview follows these switches, so what you see is what the code does.

TypeScript

The TS tab gives the same module with types. It needs @types/three for your three version. Some node values are typed any (type N = any) because TSL's types are strict about node kinds.

Troubleshooting

License

Everything you export from Rollshade (effect definitions and code, sprite sheets, images and videos) is released under CC0 1.0: no conditions, no credit needed. Use it, modify it, share it or sell it, commercial or not. The shared runtime (rollshade-fx.js, also on npm as rollshade) is under the MIT License, so keep its copyright notice. The helper functions based on psrdnoise are MIT too; keep the notice at the top of the file. three.js is MIT. Every ZIP carries LICENSE.txt. Models shown in images and videos are not covered: the preset Rollpoly models follow the Rollpoly Asset License. See License and Terms of Use for the details.