DEV Community

Cover image for 1D Perlin Noise - Understanding and Implementing the Algorithm
Gavyn Ezell
Gavyn Ezell

Posted on • Edited on

1D Perlin Noise - Understanding and Implementing the Algorithm

Table of Contents

Why I Wrote This Article

After watching this talk about perlin noise applications, I sought to build a 1D perlin noise visualizer to better understand the algorithm before moving onto 2D and 3D.

I wrote this article to test to my understanding and help others trying to learn as well.

You can check out a visualizer I built with Godot here, with the full source repo available here.

What is Perlin Noise?

Perlin noise is a type of gradient noise that is widely used in game development for procedural generation of worlds and textures.

shot of minecraft mountains

Noise can be thought of as a random scramble of values (like static on a TV). Gradient noise just aims for a more coherent scramble. Ken Perlin's original motivation for creating the algorithm was to provide better looking visual effects for movies.

A perlin noise function returns values within the range [-1, 1]. These images help visualize noise by coloring pixels white and black along this range, with a value of -1 being colored white, 1 being black, and anywhere in between is a weighted grey mixture.

value vs perlin

One simple application using these noise images is using them as height maps for generating 3D terrain in a video game


The Perlin Noise Function

Simply put:

A 1D perlin noise function takes a real number, x, as input, then returns a noise value from [-1, 1].

It is important to understand that a 2D perlin noise function receives an x and a y as input, 3D perlin noise receives an x, y, and z, and so on as the dimensions increase. Regardless of the number of dimensions, the actual function will always return a value from [-1, 1].

Function Overview

Written below is the high level form of the noise function we are about to implement. It may seem a little confusing but we'll build intuition for what each function is doing and how they are implemented.

simple algorithm

If you wanna jump to the section in the article where I actually start showing code, click here. You can also check out the perlin1d()function in this class I wrote for my visualizer here.

The Hash Function

Purpose of the Hash Function

One property of the 1D perlin noise function, is that that every integer value on our 1D number line must have an assigned noise value in the range [-1, 1].

Using the right hash function helps us accomplish this. Hash functions are deterministic - the same integer input will always produce the same output.

Take a look at this graph that plots noise value points at each integer x on the horizontal x axis. This was accomplished using a hash function.
fixed noise values

Something that could help build intuition here is imagining you're playing an open world, procedurally-generated game where your player can move through mountainous terrain. Say your player moves from chunk A, then moves very far to chunk Z. The finally moves all the way back to chunk A. We ideally want to render the same mountainous terrain we originally saw, so we need a deterministic and efficient function for recalculating the same mountains.

Picking a Hash Function

One easy hash function we can use, inspired by Ken Perlin's published code, utilizes a have uniformly distributed 256 length array with distinct values between 0-256 at each index.

Then when when we have an input integer x, we simply index into the array. This will give us an integer in the range [0, 256] which we can the perform a min-max normalization to get our final noise value between [-1, 1]

Code Starts Here

Hash Function Code

p = [151,160,137,91,90,15, 0, ...] # this has all the distinct integers between 0 and 255 

def hash(x: int):
    # indices outside our array length can just wrap around through modulo
    h = p[x % 256]

    start = -1.0
    end = 1.0
    # get our our noise value from somewhere between [-1, 1]
    final_noise = start + (h / 255.0)*(end-start)
    return final_noise
Enter fullscreen mode Exit fullscreen mode

The Lerp Function

Perlin noise uses what's called linear interpolation, or, lerp, to calculate noise values between the surrounding integer's noise values

Say 1.6 was our input x to the perlin noise function. Since we know what noise values exist at 1.0 and 2.0 through our hash function, we can linearly interpolate between these values to get our final result.

Lerp Function Code

def lerp(start, end, t): #t, is our interpolation factor
    return start + t*(end-start)

def perlin(x):
    # 1. get surrounding integers
    floor_x = floor(x)
    ceil_x = ceil(x)

   # 2. get noise values at these integers
    floor_noise = hash(floor_x)
    ceil_noise = hash(ceil_x)

    #3. get interpolation factor 
    t = x - floor_x # gives us some value in [0.0, 1.0]

    #4. interpolate to get a final noise value
    return lerp(floor_noise, ceil_noise, t)

perlin(1.6)

Enter fullscreen mode Exit fullscreen mode

Using this code and visualizing it will give us this:

single noise value on graph

And here is a fully graphed version:
lerp curve

The Fade Function

Isn't perlin noise is supposed to be smooth? Why is this graph jagged?

The final step to smoothing out this curve, is using a fade function or easing function on the interpolation factor t.

By using a fade function, we modify the interpolation factor to create smoother looking transitions between our lerp values.

There are all types of easing functions to choose from and you can check them out here. For this article, we will be using smoothstep as our chosen fade function.

Fade Function Code

def fade(t):
    # uses smoothstep
    return t * t * (3.0 - 2.0 * t)

def lerp(start, end, t): #t, is our interpolation factor
    return start + t*(end-start)

def perlin(x):
    # 1. get surrounding integers
    floor_x = floor(x)
    ceil_x = ceil(x)

   # 2. get noise values at these integers
    floor_noise = hash(floor_x)
    ceil_noise = hash(ceil_x)

    #3. get interpolation factor 
    t = x - floor_x # gives us some value in [0.0, 1.0]

    #4. interpolate to get a final noise value
    return lerp(floor_noise, ceil_noise, fade(t))

perlin(1.6)

Enter fullscreen mode Exit fullscreen mode

Final Result

And here's what that looks like graphed out:
perlin w/ smoothstep graphed

And that's it! We've covered the the basics of 1D perlin noise.

The visualizer linked at the top of this article shows some extra parameters which is actually for fractal noise, which I will also be writing a shorter followup article on. It's essentially multiple layers of perlin noise with each layer being modified.

Full Code Sample

#a scrambled array of distinct values from 0-255
p = [151,160,137,91,90,15, 0, ...] 
def hash(x: int):
    # indices outside our array length can just wrap around through modulo
    h = p[x % 256]

    start = -1.0
    end = 1.0
    # calculate our final noise value between [-1, 1]
    final_noise = start + (h / 255.0)*(end-start)
    return final_noise

def lerp(start, end, t): #t, is our interpolation factor
    return start + t*(end-start)

def perlin(x):
    # 1. get surrounding integers
    floor_x = floor(x)
    ceil_x = ceil(x)

   # 2. get noise values at these integers
    floor_noise = hash(floor_x)
    ceil_noise = hash(ceil_x)

    #3. get interpolation factor 
    t = x - floor_x # gives us some value in [0.0, 1.0]

    #4. interpolate to get a final noise value
    return lerp(floor_noise, ceil_noise, t)


Enter fullscreen mode Exit fullscreen mode

Conclusion

Knowing is half the battle!

I didn't realize how half baked my understanding of the perlin noise algorithm was until working through the implementation details and building a visualizer. Going to try and build a better habit of reading --> doing when learning this kind of stuff...

~gezell

Monkey eating banana

Resources

Listed below are some really cool resources that I have found and categorized while trying to learn perlin noise.

Videos, Papers, Articles

Practical Applications of Noise

Top comments (0)