Generative Art with Worley and Perlin Noise

In this post, I'll share a Processing example that combines Worley noise with Perlin noise to create intricate, organic patterns.

This code generates art by applying the techniques I introduced in my previous post: "The tips to make the Perlin noise creative coding works more interesting."


There is plenty of room for experimentation, so feel free to tweak the parameters and make it your own.

 

[Japanese version / 日本語版はこちら]

Visualizing Worley Noise

What is the Worley Noise?

Worley noise (also known as cellular noise) follows a straightforward logic:

  • Scatter a set of random points across the canvas.
  • For every pixel, calculate the distance to the nearest point.
  • Render colors or patterns based on that distance.

For a deep dive, check out the Wikipedia page or this excellent video by Daniel Shiffman.

 

Worley Noise: Sample Artwork and Implementation

I made some artwork using the Worley noise.


Please feel free to use this example code under the terms of the GPL.


/**
 * Worley noise : with the nearest point
 *
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * created 0.1 2021.08.07
 */

void setup() {
  size(780, 480);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
}

void draw() {

  // number of the reference points
  int pointNum  = floor(random(5.0, 30.0));
    
  // get random location points
  ArrayList<PVector> points = randomPoints(pointNum, width, height);

  // draw
  background(0.0, 0.0, 0.0, 100.0);
  drawWorley(points);

}

/**
 * randomPoints : return PVector array of random location points
 */
public ArrayList<PVector> randomPoints(float _num, float _w, float _h) {

  ArrayList<PVector> rnds = new ArrayList<PVector>();
  for (int i = 0; i < _num; i++) {
    rnds.add(new PVector(random(_w), random(_h)));
  }
  return rnds;

}

/**
 * drawWorley : draws using the Worley noise
 */
public void drawWorley(ArrayList<PVector> _ps) {

  float range  = max(width, height);
  float nsStep = 0.005;
  float dsMult = 1.0;
  
  noStroke();
  for (int iX = 0; iX < width; iX++) {
    for (int iY = 0; iY < height; iY++) {
      // get the distance with the nearest point
      float minDist = range;
      for (int i = 0; i < _ps.size(); i++) {
        float distance = dist(iX, iY, _ps.get(i).x, _ps.get(i).y);
        if (minDist > distance) {
          minDist = distance;
        }
      }
      // calculate brightness with the distance
      float nVal = minDist * dsMult * nsStep;
      float pBri = (nVal * 100.0) % 90.0;
      fill(0.0, 0.0, pBri, 100.0);
      rect(iX, iY, 1.0, 1.0);
    }
  }
}

/*
  Copyright (C) 2021- deconbatch

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;
*/


I calculated the brightness by the distance of the nearest point and drew grayscale rectangles.
It looks like a picture of cell division. The reference points are in the center of each cell.

I drew a line between the location and the nearest reference point.


You can enjoy a lot making something with the Worley noise like this.








 

Combining Worley and Perlin Noise

Injecting Worley Distance into Perlin Parameters

As seen in the Worley noise example above, the 'dist()' function is key.


float distance = dist(iX, iY, _ps.get(i).x, _ps.get(i).y);


Instead, I decided to use that distance as an input parameter for Perlin noise.


noise(distance, x, y);


The modification is simple:

Before: Brightness is derived directly from distance.


float nVal = minDist * dsMult * nsStep;
float pBri = (nVal * 100.0) % 90.0;


After: Distance acts as a coordinate in a 3D Perlin noise space.


float nVal = noise(minDist * dsMult * nsStep, iX * nsStep, iY * nsStep);
float pBri = nVal * 100.0;

 

Code Overview

The outline of the Worley noise example code above is like this.

  1. randomPoints() : Get the reference points at random locations.
  2. drawWorley() : Draw with the Worley noise algorithm.

The outline of this generative art code is the same as the example code.

 

randomPoints()

It has no collision detection logic, so the array of PVectors may contain the same location value. And also, there is the possibility that all PVectors are the same value.

 

drawWorley()

The rendering process follows these steps:

  1. On the location (x, y), get the distance to the nearest reference point.
  2. Calculate the brightness with that distance value. The Perlin noise that has the distance value in the parameter is the key of calculation.
  3. Draw a rectangle with that brightness at the location (x, y).
  4. Do the above on each location on the canvas.

 

Understanding nsStep and dsMult

These two variables are the "knobs" that control the final form:


float nsStep = 0.005;
float dsMult = 10.0;


  • nsStep: Controls the resolution or the "rate of change" within the Perlin noise field.
  • dsMult: Adjusts the scale of the distance factor, effectively stretching or compressing the patterns.

float nsStep = 0.001;
float dsMult = 10.0;



float nsStep = 0.005;
float dsMult = 30.0;


 

Experimental Ideas

The core of this generative technique lies in this line:


float nVal = noise(minDist * dsMult * nsStep, iX * nsStep, iY * nsStep);

This creates a 3D noise field. Here are a few ways to break it:

1. Reduce to 1D: Use only the distance for the noise function. The result is a set of concentric, organic rings.


float nVal = noise(minDist * dsMult * nsStep);


2. Index-based Variation: Pass the index of the nearest point into the noise function. This gives each "cell" its own unique internal pattern and creates visible boundaries.


// get the distance with the nearest point
float minDist = range;
int   minIndx = 0;
for (int i = 0; i < _ps.size(); i++) {
  float distance = dist(iX, iY, _ps.get(i).x, _ps.get(i).y);
  if (minDist > distance) {
    minDist = distance;
    minIndx = i;
  }
}
float nVal = noise(minDist * dsMult * nsStep, minIndx);


Each reference point shows its own pattern. And we can see the borderlines.

 

Conclusion

Experiment and Explore! What happens if you use the 2nd nearest or the farthest point?
Since this example is strictly grayscale, what if you introduce HSB color mapping instead of grayscale?

The possibilities are endless. Happy coding!

Next Post Previous Post
No Comment
Add Comment
comment url