Table of Contents
- Why I Wrote This Article
- What is Perlin Noise?
- The Perlin Noise Function
- Full Code Sample
- Conclusion
- Resources
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.
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.
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.
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.
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
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)
Using this code and visualizing it will give us this:
And here is a fully graphed version:
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)
Final Result
And here's what that looks like graphed out:
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)
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
Resources
Listed below are some really cool resources that I have found and categorized while trying to learn perlin noise.
Videos, Papers, Articles
Understanding Perlin Noise - A straightforward article going through the perlin noise algorithm
CMSC 425: Procedural Generation: 1D Perlin Noise - Some lecture notes from the University of Maryland
Improved Noise Implementation - The original code written by Ken Perlin for 3D Perlin Noise
Practical Applications of Noise
Shader written by Inigo Quilez - A shader that produces really nice looking scenic shot through perlin generated
Math for Game Programmers Part 4 Digging with Perlin Worms - Hidden gem GDC talk sharing perlin noise applications in 1D, 2D, and 3D
The Math Behind the Best-Selling Games: Perlin Noise - Informative video essay sharing how important and widely used perlin noise is in games
Top comments (0)