Shaders in p5.js
Write GLSL vertex and fragment shaders, pass uniforms, and apply them to 2D and 3D geometry.
What is a shader?
A shader is a program that runs on the GPU rather than the CPU. Two types matter here:
- Vertex shader — runs once per vertex; positions geometry in clip space
- Fragment shader — runs once per pixel (fragment); determines the final colour
Shaders are written in GLSL (OpenGL Shading Language). They’re extremely fast because the GPU runs thousands in parallel.
Setting up
Shaders require WEBGL mode. Create separate files (or strings) for vertex and fragment shaders:
let shd;
function preload() {
shd = loadShader('shader.vert', 'shader.frag');
}
function setup() {
createCanvas(600, 400, WEBGL);
}
function draw() {
shader(shd);
// pass uniforms
shd.setUniform('u_time', frameCount * 0.01);
shd.setUniform('u_resolution', [width, height]);
// draw a full-screen rect to trigger the fragment shader
rect(-width / 2, -height / 2, width, height);
}
The vertex shader
The minimal vertex shader passes position and texture coordinates:
// shader.vert
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vUv;
void main() {
vUv = aTexCoord;
vec4 positionVec4 = vec4(aPosition, 1.0);
positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
gl_Position = positionVec4;
}
This is the standard boilerplate for 2D full-screen effects — copy it verbatim.
Your first fragment shader
A simple gradient:
// shader.frag
precision mediump float;
varying vec2 vUv;
uniform float u_time;
uniform vec2 u_resolution;
void main() {
vec2 uv = vUv;
vec3 col = vec3(uv.x, uv.y, abs(sin(u_time)));
gl_FragColor = vec4(col, 1.0);
}
Passing uniforms from p5.js
shd.setUniform('u_time', millis() / 1000.0);
shd.setUniform('u_resolution', [width, height]);
shd.setUniform('u_mouse', [mouseX / width, 1.0 - mouseY / height]);
shd.setUniform('u_texture', myImage); // p5.Image or p5.Graphics
shd.setUniform('u_float', 3.14);
shd.setUniform('u_bool', true);
shd.setUniform('u_vec3', [1.0, 0.5, 0.2]);
Signed Distance Functions
The most expressive pattern in GLSL: define a scene as a mathematical function and render it entirely in the fragment shader:
precision mediump float;
varying vec2 vUv;
uniform float u_time;
uniform vec2 u_resolution;
// Smooth minimum — blends two shapes
float smin(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
float sdCircle(vec2 p, float r) {
return length(p) - r;
}
float sdBox(vec2 p, vec2 b) {
vec2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
void main() {
vec2 uv = (vUv - 0.5) * vec2(u_resolution.x / u_resolution.y, 1.0) * 2.0;
float t = u_time;
vec2 c1 = vec2(cos(t) * 0.4, sin(t * 0.7) * 0.3);
vec2 c2 = vec2(cos(t * 1.3 + 1.0) * 0.3, sin(t * 0.9 + 2.0) * 0.4);
float d1 = sdCircle(uv - c1, 0.3);
float d2 = sdCircle(uv - c2, 0.25);
float d = smin(d1, d2, 0.15);
// Colouring
vec3 col = vec3(0.08, 0.05, 0.12);
col = mix(col, vec3(0.5, 0.3, 1.0), 1.0 - smoothstep(0.0, 0.01, d));
col += vec3(0.1, 0.4, 0.8) * (1.0 - smoothstep(0.0, 0.1, abs(d)));
gl_FragColor = vec4(col, 1.0);
}
Applying a shader to a texture
Post-processing effects — blur, colour grade, distortion:
// glitch.frag
precision mediump float;
varying vec2 vUv;
uniform sampler2D u_texture;
uniform float u_time;
void main() {
vec2 uv = vUv;
// Chromatic aberration
float offset = sin(u_time * 3.0 + uv.y * 20.0) * 0.005;
float r = texture2D(u_texture, uv + vec2(offset, 0.0)).r;
float g = texture2D(u_texture, uv).g;
float b = texture2D(u_texture, uv - vec2(offset, 0.0)).b;
gl_FragColor = vec4(r, g, b, 1.0);
}
// In p5.js:
let pg = createGraphics(width, height); // draw your scene here
shd.setUniform('u_texture', pg);
Inline shaders (no separate files)
For sharing a single file, embed the GLSL as template literals:
const vertSrc = `
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vUv;
void main() {
vUv = aTexCoord;
vec4 p = vec4(aPosition, 1.0);
p.xy = p.xy * 2.0 - 1.0;
gl_Position = p;
}
`;
const fragSrc = `
precision mediump float;
varying vec2 vUv;
uniform float u_time;
void main() {
float v = sin(vUv.x * 20.0 + u_time) * 0.5 + 0.5;
gl_FragColor = vec4(v, vUv.y, 1.0 - v, 1.0);
}
`;
let shd;
function setup() {
createCanvas(600, 400, WEBGL);
shd = createShader(vertSrc, fragSrc);
}
Key takeaways
- Shaders run on the GPU; vertex shaders position geometry, fragment shaders colour pixels
- The minimal vertex boilerplate: scale position to clip space and pass UV coordinates
shd.setUniform()passes values from p5 to the shader each frame- Signed Distance Functions define shapes mathematically in the fragment shader
- The standard 2D full-screen effect setup:
rect(-w/2, -h/2, w, h)covers the canvas - Embed GLSL as template literals for single-file sketches, or load from separate
.vert/.fragfiles