So what is Perlin Noise? Well similarly to Diamond-Square, it's a way of generating random height-maps that can be used for a number of things. Games are generally a place they are used commonly to create random terrains. It is very simple to implement and produces much better looking results than Diamond-Square as it does not have the same artifacts. The biggest drawback to Perlin Noise is that it is slightly slower than Diamond Square and it can only generate squares. Both of these can be worked around though so lets dive into the code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PerlinNoise::PerlinNoise( int seed ) { | |
m_seed = seed; | |
m_gradient.resize( 256 ); | |
std::iota( m_gradient.begin(), m_gradient.end(), 0 ); | |
std::default_random_engine engine( m_seed ); | |
std::shuffle( m_gradient.begin(), m_gradient.end(), engine ); | |
m_gradient.insert( m_gradient.end(), m_gradient.begin(), m_gradient.end() ); | |
} |
Above we can see the constructor for our Perlin Noise class. We haven't looked at most of the constructors before but this one does more than most. In this one we fill a vector with random values. These random values are dependent on our seed and act as the "gradient" for the rest of the algorithm which will be talked about more later on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
float PerlinNoise::perlin( float x, float y, float z ) { | |
int x0 = ( int ) floor( x ) & 255; | |
int y0 = ( int ) floor( y ) & 255; | |
int z0 = ( int ) floor( z ) & 255; | |
x -= floor( x ); | |
y -= floor( y ); | |
z -= floor( z ); | |
float u = fade( x ); | |
float v = fade( y ); | |
float w = fade( z ); | |
int a = m_gradient[ x0 ] + y0; | |
int aa = m_gradient[ a ] + z0; | |
int ab = m_gradient[ a + 1 ] + z0; | |
int b = m_gradient[ x0 + 1 ] + y0; | |
int ba = m_gradient[ b ] + z0; | |
int bb = m_gradient[ b + 1 ] + z0; | |
float g0 = grad( m_gradient[ bb + 1 ], x - 1, y - 1, z - 1 ); | |
float g1 = grad( m_gradient[ ab + 1 ], x, y - 1, z - 1 ); | |
float lerp0 = lerp( u, g1, g0 ); | |
g0 = grad( m_gradient[ ba + 1 ], x - 1, y, z - 1 ); | |
g1 = grad( m_gradient[ aa + 1], x, y, z - 1 ); | |
float lerp1 = lerp( u, g1, g0 ); | |
float lerp2 = lerp( v, lerp1, lerp0 ); | |
g0 = grad( m_gradient[ bb ], x - 1, y - 1, z ); | |
g1 = grad( m_gradient[ ab ], x, y - 1, z ); | |
lerp0 = lerp( u, g1, g0 ); | |
g0 = grad( m_gradient[ ba ], x - 1, y, z ); | |
g1 = grad( m_gradient[ aa ], x, y, z ); | |
lerp1 = lerp( u, g1, g0 ); | |
float lerp3 = lerp( v, lerp1, lerp0 ); | |
float res = lerp( w, lerp3, lerp2 ); | |
return ( res + 1.0 ) / 2.0; | |
} |
Here we have our perlin method. This is where the majority of our calculations and code is. There is a lot of stuff going on here so I will cover the basics. If you are very interested in each low level idea, Ken Perlin wrote an entire paper about Perlin Noise you can read. So, carrying on. First we take the floor of our values and bitwise and them with 255 ( Read here if you don't know what bitwise operations are ). We then subtract these values from the floor of themselves. Then we see another method we haven't seen yet, fade. Let's look at fade.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
float PerlinNoise::lerp( float t, float a, float b ) { | |
return ( b - a ) * t + a; | |
} | |
float PerlinNoise::grad( int hash, float x, float y, float z ) { | |
int h = hash & 15; | |
double u = h < 8 ? x : y; | |
double v = h < 4 ? y : ( h == 12 || h == 14 ) ? x : z; | |
return ( float ) ( ( ( h & 1 ) == 0 ? u : -u ) + ( ( h & 2 ) == 0 ? v : -v ) ); | |
} | |
float PerlinNoise::fade( double t ) { | |
return t * t * t * ( t * ( t * 6 - 15 ) + 10 ); | |
} |
Here we can see all of the methods we haven't seen yet. Fade and grad are actually unique in that every implementation of Perlin Noise is supposed to/should use the same equation made by Ken. You can see it is just a simple math equation where we multiply and add some values. So carrying on, we can next see where our gradient values come on. These are chosen based from our x, y, and z, and the subsequent variables that came from them. We then see the grad method actually used. Here a series of bitwise operations change the values. These help insure randomness. They are then linear interpreted ( lerp ) with each other in our lerp method. This is just a stand linear interpolation, nothing fancy. This is done multiple times over every point. So how do we actually use this?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void PerlinNoise::to_png( const std::string &file, int width, int height ) { | |
pngwriter png( width, height, 0, file.c_str() ); | |
for ( unsigned int i = 0; i < height; i++ ) { | |
for ( unsigned int j = 0; j < width; j++ ) { | |
float x = ( float ) j / ( float ) width; | |
float y = ( float ) i / ( float ) height; | |
float n = perlin( x * 10, y * 10, 0.8 ); | |
// n = 20 * noise.perlin(x, y, 0.8); | |
// n = n - floor(n); | |
png.plot( i, j, n, n, n ); | |
} | |
} | |
png.close(); | |
} |
Above is our to_png method. We need some visual representation to output this to to see our results and that is how we do it. So lets run it and see what it looks like.
Wow, look at that! It looks random! And every time we run it it will be random. But wait... What is the commented code? Well with Perlin Noise we can easily modify the produced values to create something... different. Let's uncomment that code and run it and see what happens!
Wow! Look at that! That commented code works well for producing a wood-like texture. Pretty nifty. Playing around more can create different effects but I will let you explore them yourselves. Hope you enjoyed this and learned something new. As usual, see the full code here.