SpriteGrid

Developer documentation

Make an effect

An effect plugin is a .sgfx file: a zip holding a manifest.json and one or more fragment shaders. This page makes Burn, the effect on the home page's knight: he burns away from his feet up.

1. The manifest#

The manifest says what the effect is called, which settings it has, and which shader draws it.

manifest.json
{
  "id": "burn",
  "name": "Burn",
  "parameters": [
    { "id": "burn", "type": "float",
      "default": 0.5, "min": 0, "max": 1 },
    { "id": "ember", "type": "color",
      "default": "#FF7A1A" }
  ],
  "passes": [{
    "shader": "burn.frag",
    "uniforms": { "uBurn": "$burn",
                  "uEmber": "$ember" }
  }]
}
  • The id is how documents remember the effect. Give yours one no other effect has.
  • Each parameter is a row in the Effects panel. A float gets a number field; a color gets a swatch.
  • The pass runs burn.frag. "$burn" hands the shader the Burn setting as uBurn, and "$ember" the Ember color as uEmber.

Every field is on The manifest.

2. The shader#

A pass draws every pixel of the layer once. This one reads the pixel, works out how far the fire has come, and writes ash, flame or the pixel as it was.

burn.frag
#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uInputTex;
uniform vec2 uResolution;
uniform vec4 uEmber;
uniform float uBurn;
out vec4 FragColor;

const vec3 WHITE_HOT = vec3(1.0, 0.95, 0.7);

// A random number for each pixel.
float noise(vec2 p) {
  return fract(sin(dot(p, vec2(12.9, 78.2))) * 43758.5);
}

void main() {
  vec4 c = texture(uInputTex, vTexCoord);
  vec2 pixel = floor(vTexCoord * uResolution);

  // Burns up from the feet, ragged pixel by pixel.
  float up = 1.0 - vTexCoord.y;
  float t = up * 0.75 + noise(pixel) * 0.25;
  float fire = t - (uBurn * 1.4 - 0.2);

  if (fire < 0.0) {
    // Ash, with a few embers still glowing.
    float spark = noise(pixel + floor(uBurn * 30.0));
    bool ember = fire > -0.15 && spark > 0.9;
    c = ember ? vec4(uEmber.rgb, c.a) : vec4(0.0);
  } else if (fire < 0.06) {
    // The flames: white-hot, cooling to ember.
    c.rgb = mix(WHITE_HOT, uEmber.rgb, fire / 0.06);
  } else if (fire < 0.2) {
    // Heat glowing ahead of them.
    float k = (fire - 0.06) / 0.14;
    c.rgb = mix(uEmber.rgb * 0.6, c.rgb, k);
  }
  FragColor = c;
}

Besides its own uniforms, every pass gets vTexCoord (where the pixel is), uInputTex (the layer) and uResolution (its size in pixels). The rest are on Shaders.

3. Zip it#

Put both files in one zip, at its top (not in a folder), and name it burn.sgfx.

  • Windows: select both files, right-click, Send to ▸ Compressed (zipped) folder, then rename the zip to burn.sgfx.
  • Mac: select both files, right-click, Compress 2 Items, then rename Archive.zip to burn.sgfx.
  • A terminal: zip burn.sgfx manifest.json burn.frag

4. Load it#

In SpriteGrid, open the Effects panel's + menu and choose Open Effects Folder. Copy burn.sgfx into it.

Burn is in the + menu at once, under Other. (Give the manifest a category to pick its submenu.) Add it to a layer and drag its Burn setting.

5. Change it while you work#

SpriteGrid watches the effects folder. Zip the plugin again over the old file and the effect reloads, on every layer that has it.

If a file can't be loaded, the Effects panel says why, in red, until it's fixed. When it goes wrong has the usual reasons.

Next#

  • Start from one of SpriteGrid's own: every built-in effect is a plugin you can read.
  • Add "$schema": "https://spritegrid.com/schemas/sgfx-manifest.schema.json" to a manifest, and VS Code (or any editor that reads JSON Schemas) completes and checks it as you type.