Custom Renderers
Off-screen buffers, multi-pass compositing, SVG export, and instanced rendering for thousands of identical shapes.
createGraphics() — off-screen buffers
createGraphics(w, h) creates an invisible p5 canvas you can draw into and use as a texture or composite onto the main canvas:
let pg;
function setup() {
createCanvas(800, 600);
pg = createGraphics(800, 600);
// Draw something complex into the buffer (runs once)
pg.background(0, 0);
pg.colorMode(HSB, 360, 100, 100, 255);
for (let i = 0; i < 2000; i++) {
pg.fill(random(360), 70, 90, 80);
pg.noStroke();
pg.circle(random(800), random(600), random(2, 12));
}
}
function draw() {
background(20);
// Draw the buffer onto the main canvas
image(pg, 0, 0);
// Overlay live elements
fill(255, 200, 50);
noStroke();
circle(mouseX, mouseY, 30);
}
Multiple layers
let bgLayer, midLayer, fgLayer;
function setup() {
createCanvas(800, 600);
bgLayer = createGraphics(800, 600);
midLayer = createGraphics(800, 600);
fgLayer = createGraphics(800, 600);
drawBackground(bgLayer);
}
function draw() {
clear();
drawMiddle(midLayer);
drawForeground(fgLayer);
image(bgLayer, 0, 0);
image(midLayer, 0, 0);
image(fgLayer, 0, 0);
}
Blending modes
Both the main canvas and createGraphics() support blend modes:
blendMode(ADD); // additive — bright glowing overlaps
blendMode(MULTIPLY); // darkening
blendMode(SCREEN); // lightening
blendMode(OVERLAY); // contrast boost
blendMode(DODGE); // brighten highlights
blendMode(BURN); // darken shadows
blendMode(DIFFERENCE);// invert where images differ
blendMode(EXCLUSION); // softer difference
blendMode(HARD_LIGHT);
blendMode(SOFT_LIGHT);
blendMode(NORMAL); // reset (default)
Glow effect using ADD blending:
function draw() {
background(10);
// Draw glow layer
glowLayer.clear();
glowLayer.blendMode(ADD);
glowLayer.noStroke();
for (let i = 0; i < 5; i++) {
let alpha = map(i, 0, 4, 30, 5);
let size = map(i, 0, 4, 20, 80);
glowLayer.fill(100, 150, 255, alpha);
glowLayer.circle(mouseX, mouseY, size);
}
blendMode(ADD);
image(glowLayer, 0, 0);
blendMode(NORMAL);
fill(200, 220, 255);
noStroke();
circle(mouseX, mouseY, 10);
}
SVG output
p5.svg lets you export resolution-independent vector files:
<script src="https://cdn.jsdelivr.net/npm/p5.js-svg@1.5.1/dist/p5.svg.js"></script>
function setup() {
createCanvas(600, 600, SVG); // SVG renderer
noLoop();
}
function draw() {
background(255);
noFill();
stroke(0, 0, 0, 60);
strokeWeight(0.5);
for (let i = 0; i < 100; i++) {
let x = random(width), y = random(height);
let r = random(10, 80);
circle(x, y, r * 2);
}
save('output.svg');
}
SVG files can be opened in Illustrator or Inkscape for further editing, or sent directly to a pen plotter (AxiDraw).
Instanced rendering (WebGL)
For thousands of identical shapes with different transforms, instanced rendering sends all data to the GPU in one draw call:
// p5.js doesn't expose instancing directly — use raw WebGL
let instancedShader, posBuffer, instances;
function setup() {
let canvas = createCanvas(800, 600, WEBGL);
let gl = canvas.GL;
// ... set up instanced rendering with gl.drawElementsInstanced()
// This requires working with raw WebGL APIs alongside p5
}
For most use cases, switching to a noFill() + beginShape() batch, or grouping draw calls, achieves similar gains without raw WebGL complexity.
The drawingContext — direct Canvas 2D API access
When p5 doesn’t expose what you need, drop down to the raw Canvas API:
function draw() {
background(20);
let ctx = drawingContext; // raw Canvas 2D context
// Radial gradient — not natively in p5.js
let grad = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, 200);
grad.addColorStop(0, 'rgba(200,100,255,0.8)');
grad.addColorStop(0.5, 'rgba(100,50,200,0.4)');
grad.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
}
Also available: ctx.filter, ctx.globalCompositeOperation, ctx.getTransform(), and anything else in the Canvas 2D spec.
Pre-rendering static content
Compute expensive static content once, store it in an ImageData object, and reuse it:
let staticBg;
function setup() {
createCanvas(800, 600);
staticBg = drawStaticContent();
}
function drawStaticContent() {
let pg = createGraphics(width, height);
// ... expensive drawing ...
return pg;
}
function draw() {
image(staticBg, 0, 0); // fast — just blit the image
// draw dynamic elements on top
}
Key takeaways
createGraphics()creates off-screen buffers — draw expensive content once, reuse each frame- Layered graphics buffers (
image(layer, 0, 0)) give clean separation between background, mid, foreground blendMode(ADD)is the go-to for glowing, emissive effects- p5.svg swaps the renderer for SVG output — great for plotter art and print
drawingContextgives direct access to the Canvas 2D API for features p5 doesn’t expose- Pre-render static content into a buffer to save GPU work each frame