POSTMO Open the editor

Custom shaders

Write your own shaders

Postmo runs your GLSL as a layer. The params and colours you declare become sliders and swatches, built-in helpers keep it looping seamlessly, and code from Shadertoy, ISF or GLSL Sandbox comes in as it is.

Start

Three ways in

Write
In the editor, Fill's shader menu → New shader, or a new design's Blank tab → Custom shader. A code window opens on a starter shader; it recompiles as you type.
Import
Drop a .glsl, .frag, .fs or .fsh file onto the editor, open it with ⌘O, or paste the code. Shadertoy (mainImage), GLSL Sandbox and WebGL 1 code is rewritten for Postmo; ISF inputs become sliders, menus and colours. One pass only.
With AI
The code window's Copy AI instructions puts these rules in the clipboard for any AI chat; paste its answer back. For coding agents, the open-source kit adds a skill and a check that compiles, renders and loop-tests a shader with Postmo's own renderer.

Shape

What a shader is

The body of a GLSL ES 3.00 fragment shader. Postmo puts its prefix first and calls your main(). Uniforms declared with a note become sliders and swatches in the inspector: at most 10 params and 8 colours. The layer around it is an ordinary layer: box, crop, shape, effects, seed and colour dice all work.

// @name Custom shader
// @postmo 1
// Params and colours: a uniform with a note. They show up in the inspector.
uniform float speed;   // @param Speed 0 3 1 1
uniform float squares; // @param Squares 2 30 1 8
uniform float warp;    // @param Warp 0 1 0.01 0.35
uniform vec3 shadows;  // @color Shadows #111111
uniform vec3 glow;     // @color Glow #58ff3e
uniform vec3 edge;     // @color Edge #e9edec

// Time: uPhase goes 0 → 1 once per loop. Anything made from it loops
// seamlessly: sin(TAU * uPhase * k) with a whole k, noise through lp(p, r, z).
// For motion that runs on instead, use uTime (seconds) and uDuration.
// Place patterns with vUv (0–1) and uRes (pixels), not gl_FragCoord.
void main() {
  vec2 p = aspectP(vUv);
  // Bend the board with looping noise, then slide it a whole number of squares per loop.
  vec2 w = vec2(snoise(lp(p * 1.3, 0.6, 0.0)), snoise(lp(p * 1.3 + 5.0, 0.6, 3.0)));
  vec2 g = (p + warp * 0.25 * w) * squares + vec2(uPhase * floor(speed + 0.5) * 2.0, 0.0);
  vec2 f = fract(g) - 0.5, aa = fwidth(g);
  vec2 s = smoothstep(-aa, aa, f) * 2.0 - 1.0;
  float tile = 0.5 - 0.5 * s.x * s.y;
  vec3 c = mix(shadows, glow, tile);
  float lip = 1.0 - smoothstep(0.0, 2.0 * aa.y, abs(f.y + 0.44));
  c = mix(c, edge, lip * tile);
  outColor = vec4(c, 1.0);
}

Rules

How to write one

Reference

What your code gets

vec2 uRes
The layer's size in px.
float uPhase
0 → 1 once per loop: the time to loop with.
float uTime, uDuration
Seconds, and the design's duration: to play over time instead.
float uFrame
The frame number, for grain only.
sampler2D uTex, bool uHasTex, vec2 uTexSize
The layer's Source, when an image or video is loaded.
vec3 uSeedOff
The layer's seed; the noise functions use it.
in vec2 vUv · out vec4 outColor
Where you are on the layer (0–1, origin bottom left), and what you write.
#define TAU
6.28318530718 (there is no PI: write TAU * 0.5).

Functions

float snoise(vec3 v)Simplex noise, about −1…1; follows the seed.
float fbm(vec3 p)Five octaves of snoise: rougher detail, about −1…1.
float fbm3(vec3 p)Three octaves of snoise: cheaper fbm.
float hash12(vec2 p)A random number 0…1 for a 2D point; follows the seed.
float hash13(vec3 p3)A random number 0…1 for a 3D point; follows the seed.
vec2 loopv(float r)A point going once round a circle of radius r per loop: add it to a noise coordinate to make it loop.
vec3 lp(vec2 p, float r, float z)A noise coordinate for p that sways on x and evolves on z along a closed loop: snoise(lp(p, r, z)) loops seamlessly; r is how far (how fast) it moves.
vec2 designUv()vUv with y running down from the top.
vec2 seedJitter(float i)Zero at seed 0, else a fixed nudge in [−1, 1]² for each i: moves hand-placed shapes with the seed.
vec2 aspectP(vec2 uv)Centred coordinates scaled by the short side: round stays round in any format.
float px1080()One pixel of a 1080 px tall layer, in this layer's pixels.
vec3 ramp5(float t)Blends the first five colours (uColors[0…4]) by t in 0…1.
vec2 coverUv(vec2 uv, float zoom)uv to read the Source (uTex) so it covers the layer; zoom 1 covers exactly.

Kit

For AI coding tools

The kit is a skill (SKILL.md) and a check script, MIT licensed. Clone it, run npm install once, then:

node bin/check.mjs my-shader.glsl

It compiles the code with the renderer this site runs, says whether it loops, whether the seed changes it and what a frame costs, and writes PNG frames to look at. When it passes, drag the file onto the editor.

postmo-shader on GitHub