Perlin Noise

Posted on May 05, 2020

GameMaker Studio 2 Tutorials

Overview

Perlin Noise, created by Ken Perlin, is a type of gradient noise used in computer graphics.\ What is “gradient noise”? Instead of getting a unique random value each time you call the function, you will input a seed, and that will give you a consistent value. It does this by looking up the seed in a pre-determined grid of random values, and interpolating the values based on a gradient, or weight.

Algorithm

  1. Make an empty grid of a specific size to seed from.
  2. Iterate through grid, and generate random numbers, and save the sin and cos of that value into the grid. This will make the returning values be between [-1, 1].
  3. Input coordinates in the form of (x, y).
  4. Find the (4) grid points that form the cell that surround the coordinates.
  5. Find the gradient, or weight of the coordinates to each point. This can be simply the distance, or some smoothing function can also be used.
  6. Multiply the coordinates’ weight with each point’s value.
  7. Interpolate between each of these new values.

Code

// Function: pnoise_init()

#macro PNOISE_SIZE 256
random_set_seed(0);
global.noiseMap = [];
for (var _x = 0; _x < PNOISE_SIZE; _x++) {
    for (var _y = 0; _y < PNOISE_SIZE; _y++) {
        var _v = random_range(0, 2*pi);
        global.noiseMap[_x, _y] = [cos(_v), sin(_v)];
    }
}
/// Function: pnoise_get(x, y)

// Get coordinates
var _x = argument0 % PNOISE_SIZE;
var _y = argument1 % PNOISE_SIZE;

// Getting grid indices that contain the coordinate
var _x0 = floor(_x);
var _y0 = floor(_y);
var _x1 = (_x0 + 1) % PNOISE_SIZE;
var _y1 = (_y0 + 1) % PNOISE_SIZE;

// Determining gradients by getting distance from indices
// Example: _x = 2.75; _x0 = floor(_x) = 2; _xFrac = _x-_x0 = 0.75; _xG0 = 1-_xFrac = 0.25;
var _xFrac = _x - _x0;
var _yFrac = _y - _y0;
var _xG0 = 1-_xFrac;
var _xG1 = _xFrac;
var _yG0 = 1-_yFrac;
var _yG1 = _yFrac;

// Get grid noise values, multiply by each gradient, and interpolate
var _v0, _v1, _n0, _n1, _xV0, _xV1;

_v0 = global.noiseMap[_x0, _y0];
_v1 = global.noiseMap[_x1, _y0];
_n0 = (_xG0 * _v0[0]) + (_yG0 * _v0[1]);
_n1 = (_xG1 * _v1[0]) + (_yG0  * _v1[1]);
_xV0 = lerp(_n0, _n1, _xFrac);

_v0 = global.noiseMap[_x0, _y1];
_v1 = global.noiseMap[_x1, _y1];
_n0 = (_xG0 * _v0[0]) + (_yG1 * _v0[1]);
_n1 = (_xG1 * _v1[0]) + (_yG1 * _v1[1]);
_xV1 = lerp(_n0, _n1, _xFrac);

var _value = lerp(_xV0, _xV1, _yFrac);
return _value;

Example

Example Image Here, I rendered a map of generated perlin noise values, sampling from 0-10 in both dimensions, using a smoothstep (2x^3 - 3x^2 + 1, where x = distance from point, with range [0, 1]) for gradients. Pure black is a value of -1, while pure white is a value of 1.

Final Thoughts

There’s tons to be improved and played with when you go back to refactor this code. Try finding a smoother function to calculate gradients. Try increasing the size of the initial grid for more variety. After that, you can use this however you want, whether it’s generating terrain, or making interesting textures.

Thanks for reading!

🠬 Previous Post: Beat Detection
Next Post: Poisson Disk Sampling 🠮