Noise Palette
- Continuous Randomness
- Perlin Noise Generation
- Controllable Randomness
- Combinating Noises
- Coloration
- Results
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: Reinventing Minecraft world generation by Henrik Kniberg, at JFokus 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, it gave me the idea to create an online tool to play around with these technics and generate interesting visuals.
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 noise | 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.
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:
- interpolate the value resulting from the two top corners, depending on the horizontal position of the point;
- do the same for the bottom corners;
- 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.
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.
Input | Spline | Output |
---|---|---|
Steps | ||
Contrast | ||
Contours |
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.
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) | |
1 harmonic | |
2 harmonics | |
4 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.
Input | Color Mapping | Output |
---|---|---|
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: