Blog

Noise Palette

July 22, 2023

As often with articles on this blog, it all started with a YouTube video showcasing a clever technical process. This time, it was about terrain generation in the popular game [Minecraft](https://www.minecraft.net/): [*Reinventing Minecraft world generation by Henrik Kniberg*](https://www.youtube.com/watch?v=ob3VwY4JyzE), at [JFokus](https://www.jfokus.se/) 2022. In this video, Henrik Kniberg explains how low level world generation (thus excluding decorative features and structures) works. In the manner of the [halftone palette](halftone-palette), it gave me the idea to create an online tool to play around with these technics and generate interesting visuals.

Screenshot of the noise palette
Screenshot of the noise palette

# Continuous Randomness The main component of world generation is determining the *terrain height*. It must be random, to avoid repetition and give some interest to the player. But *one does not simply* use a basic pseudo-random number generator to determine this height, as it would be completely discontinuous, and thus completely unwakable. Instead, they use another kind of randomness: Perlin noise. You may think of it as a 2D function of the XZ plane — in Minecraft, elevation is the Y axis — where each point — each block in Minecraft, or each pixel in an image — is assigned a somewhat random value. Yet, unlike white noise, this function function is continuous (in all directions), which means transitions are smooth across adjacent blocks.

White noisePerlin noise
White noise Perlin noise
Comparison between white and Perlin noise

# Perlin Noise Generation The generation algorithm is simple and yet offers some nice properties. First you will need an initial seed, and a pseudo-random number generator. For my JavaScript implementation, I used [seedrandom.js](https://github.com/davidbau/seedrandom). Then, given a 2D space, define a grid of points regularly spaced. The interval between two points is often called the *period* of the noise. For each point, generate a random unit vector (ie. a unit vector that points to a random direction). For this to be consistent disregarding the order in which we sample the space — this is the normal behavior of an iterative number generator, that uses the last number as a seed for the next one — a "local" seed is defined at each point, derived from the initial seed and the grid point *spiral index*. In my implementation, I simply sum the two. For instance, if the initial seed is `12`, the local seed at point `(1, 1)` will be `12 + 2 = 14`. Of course, in reality, the initial seed is a much larger number. To evaluate the noise at any point of the 2D space, you first look at the random vectors of the four corners of the grid cell the point is in, then compute their impact. The impact of one corner to a point is the dot product between the random vector at that corner and the vector going from the corner to the point in question. Finally, the evaluated value is an interpolation between those dot products: 1. interpolate the value resulting from the two top corners, depending on the horizontal position of the point; 2. do the same for the bottom corners; 3. interpolate between those two values depending on the vertical position of the point. The interpolation function can be linear, but smoother functions yield better results. For details, you may check the [Perlin noise Wikipedia page](https://en.wikipedia.org/wiki/Perlin_noise). The local seed trick makes the Perlin noise behave nicely when generating several parts of the same *world*: everything will look coherent, independently of the period or any geometric deformation of the underlaying 2D space. # Controllable Randomness Although it is random, Perlin noise will always look kind of similar. To make exploring worth the effort, Minecraft developpers added more control to the randomness by adding *splines*. Each block was assigned a base value between 0 and 1. A spline is a bijection to [0, 1], which maps base values in a potentially non-continuous way. The idea is to create thresholding behaviors, vary slopes, etc.

InputSplineOutput
Perlin noise Graph of a steps curveSteps Resulting noise
Perlin noise Graph of a sigmoid like curveContrast Resulting noise
Perlin noise Graph of a spiky curveContours Resulting noise
Application of multiple splines to the previous Perlin noise

For instance, Minecraft uses a spline called *continentalness*, which defines steps to determine how far inland we are (namely, under the sea, costline, inland). # Combinating Noises Then, multiple layers may be combined to leverage specific features of each noise. For instance, Minecraft world generation relies on three different noises: *continentalness*, *erosion* and *peaks & valleys*. Each noise has different properties and intents.

Screenshot of the Henrik Kniberg conference
Noise combination for terrain height in Minecraft. Screenshot taken from Kniberg's conference.

Also, a single Perlin noise can be "enhanced" by adding *harmonics* to it: noise with the same seed, with exponentially decreasing period (ie. increasing the frequency) and amplitude. This adds details to the noise, giving a cloudy effect.

Original (0 harmonic) Perlin noise with 0 harmonic
1 harmonic Perlin noise with 1 harmonic
2 harmonics Perlin noise with 2 harmonics
4 harmonics Perlin noise with 4 harmonics
Addition of multiple harmonics

# Coloration Finally, the output is a 2D image in greyscale, which can be colorized by using another kind of spline: instead of mapping [0, 1] to [0, 1], it maps it to [0, 255]³, ie. a space of colors. A simple way to do this is to use a linear gradient with controllable color stops.

InputColor MappingOutput
Perlin noise Pink-yellow-blue gradient Output of coloration
Perlin noise Rainbow gradient Output of coloration
Perlin noise Blue-yellow-green-brown gradient Output of coloration
Application of multiple color mappings

# Results The tool to experiment and fiddle around with those concepts is accessible online at https://chalier.fr/noise-palette/. You can add several noises, tune their parameters, colorize the output, and export the result as an image (even large ones, given your computer is strong enough). Here are some of the results I got:

Linear interpolation result
Linear interpolation [config]

Terrain map
Terrain map [config]

Clouds
Clouds [config]

Red curve on black background
Curve [config]

Night Sky
Night sky [config]

Wood texture
Wood texture [config]

Abstract pattern
Abstract pattern [config]

Abstract pattern
Abstract pattern [config]

Abstract pattern
Abstract pattern [config]

Acid pattern
Acid [config]

Camouflage pattern
Camouflage [config]

Result of seed overflowing
Overflow (when the seed is larger than JavaScript's MAX_SAFE_INTEGER, repetitive patterns start to apply) [config]

Imitation of a PCB board texture
Circuit board (relying on the same overflowing effect) [config]