Generative Typography

Noise and Randomness

Students will leave the session with an understanding of how to incorporate Perlin Noise into compositions.

Class Agenda

Sketching & Presentations 8:30 - 9:30am
BREAK 15min
Code Lecture 9:45 - 11:00am
BREAK 60min
Workshop 12:00 - 2pm

Morning Sketch

Continuing our exploration of generative artwork. Find and recreate two pieces from the following early generative artists and recreate it: Vera Molnár, Manfred Mohr, Frieder Nake, Georg Nees, A. Michael Noll.

Weekly Assignment

Recursion, 3D and Noise. Create a typographic composition using at least one of these concepts.

Weekly Assignment 2

Create a 3D letter using textToPoints and implement orbitControl so that the user can explore the composition. Similar to the Loading screen assignment create an effect around your composition that occurs on a time loop.

Further Resources

Code Lecture

Randomness

In creative coding randomness allows us to build more variety into our system without having to specify the exact parameters. It is a way of turning over control to the computer as we search for the exact conditions we want to use in our final composition. Up until now we have been using random() but when we are working with motion this function has its downsides.

function setup() {
  createCanvas(400, 400);

  frameRate(2);
}

function draw() {
  background(220);

  var x = random(width);

  circle(x, height / 2, 10);
}

In the example above the randomness is almost too random and sometimes we instead want a function that is more smooth and organic.

Perlin Noise

In p5.js we have a function called noise() that uses the concept of Perlin noise, named after its inventor, Ken Perlin. Perlin noise was created to provide semi-random variations that are continuous and smooth. You can use it in a number of different ways to create textures, terrains and generally randomized outputs that feel smoother.

let noiseX = 0;

function setup() {
  createCanvas(400, 400);

  noiseX = random(1);
}

function draw() {
  background(220);

  var x = width * noise(noiseX);

  circle(x, height / 2, 10);

  noiseX += 0.025;
}

Noise can be applied to any values in our sketches. Let's create a grid of circles

var gridSize = 10;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);

  var cellSize = width / gridSize;

  for (var rowIndex = 0; rowIndex < gridSize; rowIndex += 1) {
    for (var columnIndex = 0; columnIndex < gridSize; columnIndex += 1) {
      var posX = cellSize * columnIndex;
      var posY = cellSize * rowIndex;

      circle(posX + cellSize / 2, posY + cellSize / 2, cellSize);
    }
  }
}

Using the rowIndex and columnIndex we can use 2D Noise and randomize the size of our circles based on their position.

var gridSize = 10;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);

  var cellSize = width / gridSize;

  for (var rowIndex = 0; rowIndex < gridSize; rowIndex += 1) {
    for (var columnIndex = 0; columnIndex < gridSize; columnIndex += 1) {
      var posX = cellSize * columnIndex;
      var posY = cellSize * rowIndex;
      var size = noise(rowIndex, columnIndex) * cellSize;

      circle(posX + cellSize / 2, posY + cellSize / 2, size);
    }
  }
}

Adding textToPoints

Let's step through how we can apply textToPoints to our new Noise technique.

var bebasFont;
var bounds;
var points = [];
var fontSize = 500;
var rotateAmt = 0;
var noiseX;
var noiseY;

function preload() {
  /**
   * Please note that this is a font hosted by another project of mine.
   * There is no guarantee that this url will function in the future.
   */
  bebasFont = loadFont("https://garnet.website/static/fonts/BebasNeue.ttf");
}

function setup() {
  createCanvas(500, 500);

  textFont(bebasFont);
  textSize(fontSize);
  textAlign(CENTER, CENTER);
  fill("white");
  noStroke();
  angleMode(DEGREES);

  // create the points and points
  points = bebasFont.textToPoints("A", 0, 0, fontSize, {
    sampleFactor: 0.5,
    simplifyThreshold: 0,
  });
  bounds = bebasFont.textBounds("A", 0, 0, fontSize);

  // set up noise
  noiseX = random(1);
  noiseY = random(1);
}

function draw() {
  background(220);

  // translate the entire canvas
  translate(width / 2, height / 2);
  // align the bounds
  translate(-bounds.w / 2, bounds.h / 2);

  points.forEach((pt, index) => {
    var offset = noise(pt.x + noiseX, pt.y + noiseY) * 20;
    var x = pt.x + offset;
    var y = pt.y + offset;

    circle(x, y, 5);

    // DEBUG
    // push();
    // fill("red");
    // circle(pt.x, pt.y, 5);
    // pop();
  });

  noiseX += 0.1;
  noiseY += 0.1;
}