Function

@shaders

A GLSL fragment shader, run on the GPU with WebGL. Every pixel runs the same main() and writes its own color to FragColor.

Syntax

  • @shaders(<fragment source>)
  • @shaders(fragment { … } texture0 { <doodle code> })
  • @shaders<width>x<height>(…)

The body is either fragment shader source, or named sections: fragment, vertex and any number of texture… sections. Digits after the name set the image size in CSS pixels (@shaders800 is square). Without them the image takes the size of the element and redraws when it resizes.

Put it in @content to draw a live <canvas>. It also works as a background, but then every frame is encoded into a new image, which is much slower when the shader animates.

Coordinates and color

The version line, precision, the FragColor output and the uniforms are declared for you. u_resolution is the canvas size in device pixels, so gl_FragCoord.xy / u_resolution runs from 0 to 1, starting at the bottom-left corner:

@grid: 1 / 60vmin;
@content: @shaders(
  void main() {
    vec2 p = gl_FragCoord.xy / u_resolution;
    FragColor = vec4(p.yx, .8, 1);
  }
);

An undeclared pos is the centered position, with the shorter side from −0.5 to 0.5, so circles stay round on any aspect ratio.

Animation

u_time is the time in seconds. A shader that reads u_time, u_timeDelta or u_frameIndex is redrawn every frame; any other shader draws once.

@grid: 1 / 60vmin;
@content: @shaders(
  void main() {
    float a = atan(pos.y, pos.x);
    float r = length(pos);
    vec3 c = cos(vec3(1, 2, 3) + 2.*a + 12.*r - u_time);
    FragColor = vec4(.4 + .6*c, 1);
  }
);

The pointer

u_mouse is the pointer position in device pixels, from the bottom-left corner. It starts at the top-left corner and only updates in a shader that is redrawn every frame, so this one reads u_time too:

@grid: 1 / 60vmin;
@content: @shaders(
  void main() {
    vec2 p = gl_FragCoord.xy - u_mouse;
    float d = length(p) / u_resolution.y;
    float v = step(0., sin(80.*d - 4.*u_time));
    FragColor = vec4(vec3(v), 1);
  }
);

Doodle textures

A section named texture… holds doodle code. It is rendered at the size of the shader and bound as a sampler2D of the same name. The shader then goes in a fragment section:

@grid: 1 / 60vmin;
@content: @shaders(
  fragment {
    void main() {
      vec2 uv = gl_FragCoord.xy / u_resolution;
      uv.x += sin(uv.y * 20. + u_time) * .02;
      FragColor = texture(texture0, uv);
    }
  }
  texture0 {
    @grid: 8 / 100%;
    background: @p(#000, #e6437d, #ebbf4d);
  }
);

Sections in custom properties

$name anywhere in the body inserts the text of the custom property --name. GLSL can live in --fragment: @raw(…), and a texture section whose whole body is $name draws the doodle in --name: @doodle(…). That doodle reads the outer variables, like any nested @doodle():

@grid: 1 / 60vmin;
--c: #000, #e6437d, #ebbf4d;
--fragment: @raw(
  void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    uv.x += sin(uv.y * 20. + u_time) * .02;
    FragColor = texture(texture0, uv);
  }
);
--texture: @doodle(
  @grid: 8 / 100%;
  background: @p(--c);
);
@content: @shaders(
  fragment { $fragment }
  texture0 { $texture }
);

An unknown name skips the shader. Text stored in a variable is one line, so keep // comments and #define lines in the section itself.

Shadertoy code

A shader with mainImage() works as is. iResolution, iTime, iTimeDelta, iFrame and iMouse are mapped to the uniforms below (the zw of iMouse is always 0), and iChannel0, iChannel1, … are the texture sections in order:

@grid: 1 / 60vmin;
@content: @shaders(
  void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    vec3 col = .5 + .5 * cos(iTime + uv.xyx + vec3(0, 2, 4));
    fragColor = vec4(col, 1);
  }
);

Reference

Uniforms

UniformValue
vec2 u_resolutioncanvas size in device pixels (CSS size × devicePixelRatio, at most 4096)
float u_timeseconds since the shader started
float u_timeDeltaseconds since the previous frame
int u_frameIndexframe counter
vec2 u_mousepointer position in device pixels, from the bottom-left corner
vec2 u_seedx from the seed alone, y from the seed and the cell; both 0 to 1

Shortcuts

gl_FragColor may stand for FragColor, and texture2D() for texture(). pos is defined when the shader uses it without declaring it. // comments are allowed inside the body. A vertex section replaces the default vertex shader.

See also