What you'll create

The aim of this workshop is understand some applications of Perlin Noise, and use it to create some colourful terrain! Once you can generate this terrain as a 2D image, you could apply this in 3D contexts as well to create Minecraft-like worlds.

Click to generate a new map

We will be coding in JavaScript but don't worry about knowing the ins and outs of the language; you should be able to pick it up as we go!

Using p5.js

We will be using JavaScript and a library called p5.js, made by the lovely Processing Foundation.
  1. To get started, create an account for the web editor

  2. Open a new sketch (File->New)

What is a sketch?

A sketch is a small program that will work with p5.js to produce a simulation. Just like a game, your simulation will have frames (an image of your simulation) and p5.js will aim to show 60 frames a second. You'll be able to code what happens on certain events e.g. just before a frame is shown, just before the simulation starts, when the mouse is clicked.

In this lab, since we're just drawing an image, we'll only concern ourselves with drawing before the simulation starts and when the mouse is clicked.

Your new sketch should have 2 functions:

We're going to ignore the draw() function, since we'll just be using the setup function. In other words, in this lab, just delete the draw() function. We create the canvas (arguments being the width and length) in setup() ; this is something you only do once pretty much regardless of the simulation.
                
function setup() {
    createCanvas(400, 400);
    let redV = 128; // if you're new to js, use "let" to declare variables
    let greenV = 12; 
    let blueV = 128;
    background(redV, greenV, blueV); // my favourite colour; purple!
}
                
            
RGB values have R,G,B values ranging from 0-255

Mini-Task: Try running the above simulation! Maybe change the variables to display your own favourite colour.

Creating Random Noise

Click to generate new random noise

Let's dive straight into generating random noise! We'll need to know a couple things:

  1. Drawing a pixel to the screen, with a specific colour
  2. Generating random values
  3. Turning a random value into a colour

Let's tackle (1) first!

Pixels

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

    // the eyes
    point(25, 25);
    point(28, 25);

    // the smile
    point(25, 30);
    point(26, 30);
    point(27, 30);
    point(28, 30);

    point(24, 29);
    point(23, 28);

    point(29, 29);
    point(30, 28);
}
                
            

We can use the point() function to draw a pixel at specified X,Y coordinates. What about the colour?

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

    // eye colour
    stroke(0, 0, 255);

    // the eyes
    ...

    // smile colour 
    stroke(255, 0, 0);

    // the smile
    ...
}
            
        

We can use the stroke() function to determine the colour of the pixels drawn from that point on; we can supply it with 3 values to specify an RGB colour. If we're only using grayscale colours, then we can provide just a single value (to represent the brightness or "value" of the colour) e.g. 255 for pure white, 0 for pure black.

Using this knowledge, can you guess what the following code does?

            
function setup() {
    createCanvas(400, 400);
    
    for(let y = 0; y < 400; y++){
        for(let i = 0; i <= 255; i++){
        stroke(i);
        point(i,y);
        }
    }
}
            
        

Mini-Task: Run the above simulation, and see if you guessed what it does correctly!

Random Values

A key thing to remember about p5.js is that ultimately, it is still JavaScript. Therefore, we can use JavaScript functions and libraries with p5.js.

To handle randomness, we'll use js' Math library; in particular, the function Math.random()! Let's use the JavaScript console to test it out.

            
for(let i = 0; i < 20; i++){
    console.log(Math.random()); // values between 0-1
}
            
        

Okay cool, there's only a slight problem now; we get random values in the range 0-1, but the colour values are in the range 0-255. All we need to do is multiply up :)

Use the docs!

If you ever have questions about p5 functions, then please refer to the excellent documentation!

Task: Random Noise

It's now your job to replicate the random noise sketch! Use the tools above and the template below to create some random noise.

            

const width = 400; // these are "constant" variables, as they don't change
const height = 400; // made to be variables since they're used in 2 places (iterating and createCanvas)

function drawRandomNoise(){
    for(let x = 0; x < width; x++){
        for(let y = 0; y < height; y++){
            // FILL ME
        }
    }
}
function setup() {
    createCanvas(width, height);
    drawRandomNoise();
}
            
        
Generally, taking this out into its own function for this is a good idea; especially since this function's sole purpose is drawing random noise

Perlin Noise

Okay so we've programmed random noise which is "incoherent" noise - you can have sharp constrasts between neighbouring pixels. If we want nice smooth gradients, then we can use perlin noise!

We can definitely see that the noise is more ordered than random noise, however we want it to be less sharp; we want it to be smoother.

Smoothing it out

So how do we achieve a smoother noise? We can figure this out by looking at 1D noise, and think about sampling 10 points from perlin noise.

From the above, we can see that the values sampled can be quite different. You can think of this as sampling the 10 points at $x=1, 2, 3, \dots$

What if we sampled points more frequently instead?

From the above, we can see that the values sampled are a lot closer; you can think of this as sampling the 10 points at $x=0.2, 0.4, 0.6 \dots$

So the general takeaway is that we can generate smoother noise, by sampling more frequently. How do we do that? We can just divide x,y before feeding it into the noise function!

Task: Smooth Noise

Use the tools above to generate some smooth perlin noise! Have a variable called the smoothing factor, and change its value to see how the noise varies.

Creating Islands

We now have the tools to create islands! By designating pixel with noise values past a certain threshold as "land" and the rest as "ocean", we can generate islands. Since the noise() function returns a value between 0 and 1, we should have a threshold value between 0 and 1.

            
    
const threshold = 0.55;

function getColour(noiseVal){
    if(noiseVal > threshold){
        return color(239, 221, 111); // sandy yellow (island)
    }else{
        return color(0, 157, 196); // ocean blue
    }
}
            
        

Note that the above function returns a "colour" object; if you want to work with large p5.js programs, then you should really be using classes and objects. Now we can use the returned colour object like so:

            
let colour = getColour(noise(x/sf, y/sf)); // colour object
stroke(colour); // stroke accepts the colour object directly (instead of number values representing a colour)
            
        

Task: Desert Islands

Create desert island generation by modifying your smooth noise task, with the above tools. It should look something like below:

More Detailed Islands

Sometimes, you want more detailed islands; you want the overall shape of the islands to be the same so you have the same smoothness factor, but you want there to be some more granular detail. You might want your beaches to have some roughness to its terrain. After all, terrain isn't perfectly smooth in nature!

You can add this granular detail by adding layers of perlin noise together; each of these layers being called "octaves". Typically, you'll have a large amplitude perlin noise wave which describes the general overall shape, and then you'll have a smaller amplitude perlin noise waves to describe the finer details. These smaller waves usually have higher frequency, which will give the islands a distinct roughness.

You can increase the number of octaves and how much the smaller waves contribute to the noise by using p5's noiseDetail() function. Read up on the details here.

You can also add more "detail" to the islands by creating multiple threshold levels, like in the example at the top of this webpage! An example of a more complicated threshold colour function:

            
const snowcapThreshold = 0.75;
const mountainThreshold = 0.6;
const grassThreshold = 0.4;
const shallowWaterThreshold = 0.35;

function getColour(noiseVal){
    if(noiseVal > snowcapThreshold){
    return color(219, 219, 219); // snowcap white
    }else if(noiseVal > mountainThreshold){
        return color(112, 112, 112); // mountain gray
    }else if (noiseVal > grassThreshold){
        return color(75, 139, 59); // grass green
    }else if (noiseVal > shallowWaterThreshold){
    return color(0, 157, 196); // light blue
    }else{
    return color(0, 147, 186); // slightly darker blue
    }
}
            
        

Final Task: Create your own islands!

You have everything you need to create your own amazing terrain generation! You can take my generator(s) (displayed at the top of the page) as inspiration, but I'm sure you'll be able to create a better looking generator than mine.

You can view the code for my two generators here:

Tip: Getting good terrain with perlin noise is often a case of just tweaking your parameters (octaves, amplitude/frequency, smooth factor etc) until they're just right.

Going Further

If you want to make your generator even cooler, then you can explore more procedural generation techniques. Natural next steps include: