Learn Creative Coding Series):There's something deeply satisfying about grids.
A single shape is just a shape. Repeat it 400 times in a grid and suddenly it's a pattern, a texture, a visual rhythm. Add small variations and it becomes something alive. This is one of the most powerful ideas in generative art: repetition with variation.
A nested for-loop gives you a grid. Nothing fancy:
function setup() {
createCanvas(500, 500);
background(240);
noStroke();
let spacing = 25;
let size = 18;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
fill(80, 130, 200);
ellipse(x, y, size, size);
}
}
}
A grid of blue dots. Clean, orderly, boring. But this is our canvas — now we start breaking it.
The simplest trick: make one property depend on position.
function setup() {
createCanvas(500, 500);
background(20);
noStroke();
let spacing = 25;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
// size depends on distance from center
let d = dist(x, y, 250, 250);
let size = map(d, 0, 350, 20, 2);
fill(255, 200);
ellipse(x, y, size, size);
}
}
}
dist() calculates the distance from each dot to the center. map() converts that distance into a size — big in the center, small at the edges. Instantly the grid has a focal point.
map(value, fromLow, fromHigh, toLow, toHigh) is one of p5.js's most useful functions. It rescales a value from one range to another. You'll use it everywhere.
Now let's make it organic:
function setup() {
createCanvas(500, 500);
background(20);
noStroke();
let spacing = 20;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
let n = noise(x * 0.01, y * 0.01);
let size = n * 20;
fill(n * 200 + 55, 100, 200 - n * 100, 180);
ellipse(x, y, size, size);
}
}
}
The noise field controls both size and color. Clusters of large bright dots next to clusters of small dark dots. It looks geological — like a cross-section of rock under a microscope.
Instead of varying size, vary rotation. This is where push()/pop() from episode 3 becomes essential:
function setup() {
createCanvas(500, 500);
background(245);
let spacing = 30;
let size = 20;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
push();
translate(x, y);
let angle = noise(x * 0.01, y * 0.01) * TWO_PI;
rotate(angle);
stroke(40);
strokeWeight(1.5);
noFill();
rect(-size/2, -size/2, size, size);
pop();
}
}
}
A grid of rectangles, each rotated by a different amount based on noise. The rectangles near each other have similar rotations because noise is smooth. You get this flowing, fabric-like pattern from nothing but rotated squares.
Grids of small lines are one of the classic generative art motifs. Vera Molnar did this in the 1960s with a plotter and it still looks stunning.
function setup() {
createCanvas(500, 500);
background(250);
let spacing = 15;
let len = 12;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
push();
translate(x, y);
let angle = noise(x * 0.008, y * 0.008) * PI;
rotate(angle);
stroke(30, 150);
strokeWeight(1.2);
line(-len/2, 0, len/2, 0);
pop();
}
}
}
Short lines following a noise flow field. This is essentially a simplified version of a technique called "flow fields" — we'll go much deeper on this later, but the basic idea is right here: direction controlled by noise.
Real compositions layer multiple grids on top of each other:
function setup() {
createCanvas(600, 600);
background(15);
noStroke();
// Layer 1: large, sparse, semi-transparent circles
for (let x = 40; x < width; x += 80) {
for (let y = 40; y < height; y += 80) {
let n = noise(x * 0.005, y * 0.005);
fill(200, 80, 80, n * 80);
ellipse(x + random(-10, 10), y + random(-10, 10), n * 70, n * 70);
}
}
// Layer 2: medium, denser
for (let x = 20; x < width; x += 40) {
for (let y = 20; y < height; y += 40) {
let n = noise(x * 0.01 + 100, y * 0.01 + 100); // offset to get different noise
fill(80, 150, 200, n * 60);
ellipse(x, y, n * 25, n * 25);
}
}
// Layer 3: small, very dense, like stars
for (let x = 5; x < width; x += 10) {
for (let y = 5; y < height; y += 10) {
if (random() > 0.7) { // only 30% of positions
fill(255, random(30, 100));
ellipse(x + random(-3, 3), y + random(-3, 3), 2, 2);
}
}
}
}
Three layers with different densities, sizes, colors, and noise offsets. The random(-10, 10) offsets break the rigid grid alignment — the elements feel placed, not plotted.
Notice the + 100 offset on the second layer's noise input. Without it, both layers would follow the same noise pattern and overlap perfectly. By sampling a different region of the noise space, each layer has its own flow.
Make it move:
function setup() {
createCanvas(500, 500);
}
function draw() {
background(20);
noStroke();
let spacing = 25;
let t = frameCount * 0.02;
for (let x = spacing; x < width; x += spacing) {
for (let y = spacing; y < height; y += spacing) {
let n = noise(x * 0.015, y * 0.015, t);
let size = n * 22;
let hue = map(n, 0, 1, 150, 300);
fill(hue % 255, 100 + n * 100, 200, 180);
ellipse(x, y, size, size);
}
}
}
Same grid, but the noise field evolves over time. Dots swell and shrink, colors shift. It breathes.
Working with grids taught me a few things about generative art in general:
Constraint breeds creativity. A rigid grid is a constraint. But within it you have infinite freedom — size, color, rotation, offset, shape, opacity. The grid gives structure. The variation gives life.
Small changes, big impact. You don't need ten different tricks per piece. One grid + one property varying smoothly = compelling work. Adding a second variable doubles the complexity.
Symmetry is boring, near-symmetry is beautiful. A perfectly symmetric grid is wallpaper. A grid with noise-driven variation feels handmade. The imperfection is the point.
dist() for distance calculations, map() for rescaling values+ 100) let multiple layers use different noise patternspush()/pop() are essential when rotating individual grid elementsNext episode: interactivity. We'll make sketches that respond to mouse, keyboard, and touch. Time to let the viewer become part of the art.
Sallukes! Thanks for reading.
X