Pink/Red Liquid using Perlin Noise

CC-BY-2.0

The most common and useful approach when procedurally generating anything, is to use noise: randomly generated numbers.

Of course, purely random numbers are kind of useless to us, as we need to be able to test things (i.e.: reproducibility), so we tend to use Pseudorandom Number Generators that take some kind of seed and give us seemingly random numbers in return.

Noise can be used in many, many, many ways, such as...

  • Generating textures from scratch
  • Generating terrain by interpreting noise as height
  • Generating terrain via voxel density volumes
  • Creating interesting visual effects
  • Enhancing existing textures with graininess;
    e.g to make it look old/used/worn.
  • And so, so, much, more...
    Seriously, we couldn't list them, even if we tried!

Noise-based generation generally happens via a noise pipeline, consisting of three parts:

This is just a basic framework to get the concept across; it can be changed and extended in a variety of ways once understood.
There are no hard rules... be creative!

Noise Functions

All uses of noise begin with a noise function.

That is, a function taking an ℕ-dimensional sample position and returning a noise sample in the [-1, +1]-range.

/// An abstract noise function.
/// 
/// - Takes a position.
/// - Returns a number.
/// - May contain math.
/// - A *lot* of math.
fn noise(position: Vecℕ) -> f32;

Calling such a function means to sample the noise function.

A note on number ranges:

While most libraries return numbers in the [-1, +1]-range, some may use the [0, +1]-range, or something else entirely even; read the documentation, or code, to make sure!

Though conversions are usually rather easy; for example:

  • To turn [0, +1] into [-1, +1]:
    Multiply by two and subtract one: v * 2 - 1

  • To turn [-1, +1] into [0, +1]:
    Add one and divide by two: (v + 1) / 2
    Mind the parenthesis!

Now with that preface out of the way, let's proceed to the actual noise functions!

White Noise & Value Noise

White Noise

CC0

If you have ever seen a Television that receives it's video as analog signals via an antenna, you might have seen what happens when the antenna isn't receiving anything: The screen becomes a sea of noise; commonly referred to as white noise.

White noise by itself is completely random, with no discernible patterns between/across the samples, which isn't really useful by itself.

However, take that very noise and scale it up/zoom into it, linearly interpolating between the individual samples, and it will begin to resemble something much more interesting: value noise.

Unfortunately, while value noise tends to be ludicrously fast to generate, it's quality isn't particularly great due to linear artifacts. When used for terrain generation, these artifacts will cause strange patterns to emerge, which is absolutely not what one wants, so we will have to find something better than value noise...

Perlin Noise

Perlin Noise

CC0

Now, again, value noise is extremely simple and fast to generate, but unfortunately has linear artifacts... which is where Perlin noise comes in!

Invented in the early 1980s by Ken Perlin, Perlin noise improves on value noise by interpolating between randomly generated gradients on a grid, instead of plain random values.

Explaining the exact algorithm is, (un)fortunately, out-of-scope for this article; since there are many implementations of it, finding one should not be a problem.

The 'blobby' nature of Perlin already makes it perfect for many use cases, though if one looks closely there appear to be... straight lines? Let's keep going!

Simplex Noise

Now, is Perlin noise perfect? Sadly, no: It too has artifacts, caused by the sampling being done on a regular grid, making values appear to be axis-aligned and/or biased on the diagonal.

This is visible in the top left of the previous example image as a straight 'hill'.

If you are interested in the inner workings and actual reasons behind these artifacts, we highly recommend reading KdotJPG's excellent blog post The Perlin Problem: Moving Past Square Noise for more!

Thankfully, Ken Perlin noticed the issue circa 2001 and remedied this by coming up with a new noise function: simplex noise!

The solution is surprisingly simple: Instead of sampling and interpolating the random numbers in a square grid, he used a triangular ('simplex') grid, which has several advantages:

  1. The artifacts are no longer visible, due to the grid of pixels no longer being aligned with the grid of gradient positions.
  2. A triangle is the smallest polygon enclosing an area of two dimensional (or higher) space, so the algorithm only needs to evaluate and interpolate between three corners, instead of four, which is much faster!
  3. The algorithm can be extended into higher dimensions (3D, 4D, 5D, and higher!), without having to evaluate exponentially more corners, unlike Perlin noise, making it even faster in this case!

Due to patent shenanigans, simplex noise was not possible to freely implement for quite some time, leading to the creation of open simplex noise. Thankfully, the patent has run out, so go forth and code!


With that we have seen enough noise functions... on to noise operations!

Noise Operations

Noise operators are functions that accept noise modules (which are noise-functions and -operators), creating new noise modules from them. This allows for the creation of much more complex/detailed noise.

They are split into these four categories:

  • Modifiers change the output of a noise module.

  • Combiners mix the output of several noise modules.

  • Selectors mix the output of several noise modules,
    controlled by another noise module.

  • Transformers work with the sample positions going into the modules.

Modifier Operations

Even with just one noise sample, we can do many things... for example:

  • Shift it by adding/subtracting
  • Multiply it to scale its amplitude
  • Put it through a spline or easing function
  • Change its slope with pow(v, …)
  • Convert to other number ranges
  • etc. etc.

Here is an example of a cubic easing modifier, written in Rust:

// Assuming we already have a noise function...
// - `p` is the *sample position*
// - `v` is the *noise sample*, here in the [-1, +1]-range
let mut v: f32 = noise(p);

// With help from <https://github.com/prideout/par/blob/master/par_easings.h>,
// let's apply a cubic-easing function to our noise sample!

// This one takes values in the [0, +1]-range...
fn ease_cubic(mut t: f32, b: f32, c: f32, d: f32) {
  t /= d / 2.0;
  if t < 1.0 {
    return c / 2.0 * t * t * t + b;
  }
  
  t -= 2.0;
  return c / 2 * (t * t * t + 2) + b;
}

// ...so we first have to convert our sample from [-1, +1] to [0, +1]...
v = (v + 1.0) / 2.0;

// ...and then we can apply the function!
v = ease_cubic(v, 0, 1, 1);

You might notice that we did not define what the sample position p is, or where the result goes... and this is on purpose!

Modifiers are independent of each other, so you can mix-and-match them however you like, there are no limits to your creativity here!

This section is a stub. Perhaps help us expand it?

Combiner Operations

This section is a stub. Perhaps help us expand it?

Selector Operations

This section is a stub. Perhaps help us expand it?

Transformer Operations

This section is a stub. Perhaps help us expand it?

Noise Interpretation

Once you've obtained a final value from your noise evaluation, you will need to interpret it.

Usually this means either putting the sample through a transfer function, thresholding it or using it directly as-is.

This section is a stub. Perhaps help us expand it?

Transfer Function

This section is a stub. Perhaps help us expand it?

Thresholding

This section is a stub. Perhaps help us expand it?

References

TODO: Add more references.

Noise Libraries

Writing correct noise-generating code can be quite hard, so it is generally recommended to try using a library first...

  • libnoise
    Great documentation, but quite old, slow and licensed under LGPL, which is a no-go for most projects. Notable because it has the best documentation for organizing/conceptualizing noise primitives and various operations on noise, so it's a good idea to browse through the various modules.
  • OpenSimplex2
    Various improvements over Perlin Noise and under a CC0 license.
  • FastNoiseLite
    Fast noise generation with implementations in several languages, several kinds of noise and a GUI to preview noise.
  • FastNoise2
    Newer version of FastNoiseLite, by the same author, written entirely in C++. One of the, if not the, fastest noise generation libraries.

Explainer Videos