@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
| Uniform | Value |
|---|---|
vec2 u_resolution | canvas size in device pixels (CSS size × devicePixelRatio, at most 4096) |
float u_time | seconds since the shader started |
float u_timeDelta | seconds since the previous frame |
int u_frameIndex | frame counter |
vec2 u_mouse | pointer position in device pixels, from the bottom-left corner |
vec2 u_seed | x 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.