Skip to content

JavaScript implementation of simplex noise 2D algorithm with fBm and SplitMix32 in 550 bytes.

License

Notifications You must be signed in to change notification settings

attilabuti/SimplexNoise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SimplexNoise

Usage

// simplex_noise_2d.js
seed(1337);
let value = fbm(x, y, octaves, amplitude, frequency, persistence, lacunarity);

// simplex_noise_2d.min.js
s(1337);
let value = b(x, y, octaves, amplitude, frequency, persistence, lacunarity);

Simplex noise

Simplex noise is the result of an $n$-dimensional noise function comparable to Perlin noise ("classic" noise) but with fewer directional artifacts and, in higher dimensions, a lower computational overhead. Ken Perlin designed the algorithm in 2001 to address the limitations of his classic noise function, especially in higher dimensions.

The advantages of simplex noise over Perlin noise:

  • Simplex noise has lower computational complexity and requires fewer multiplications.
  • Simplex noise scales to higher dimensions (4D, 5D) with much less computational cost: the complexity is $O(n^2)$ for $n$ dimensions instead of the $O(n2^n)$ of classic noise.
  • Simplex noise has no noticeable directional artifacts (is visually isotropic), though noise generated for different dimensions is visually distinct (e.g. 2D noise has a different look than 2D slices of 3D noise, and it looks increasingly worse for higher dimensions).
  • Simplex noise has a well-defined and continuous gradient (almost) everywhere that can be computed quite cheaply.
  • Simplex noise is easy to implement in hardware.

Whereas classical noise interpolates between the gradients at the surrounding hypergrid end points (i.e., northeast, northwest, southeast and southwest in 2D), simplex noise divides the space into simplices (i.e., $n$-dimensional triangles). This reduces the number of data points. While a hypercube in $n$ dimensions has $2^n$ corners, a simplex in $n$ dimensions has only $n+1$ corners. The triangles are equilateral in 2D, but in higher dimensions the simplices are only approximately regular. For example, the tiling in the 3D case of the function is an orientation of the tetragonal disphenoid honeycomb.

Simplex noise is useful for computer graphics applications, where noise is usually computed over 2, 3, 4, or possibly 5 dimensions. For higher dimensions, n-spheres around n-simplex corners are not densely enough packed, reducing the support of the function and making it zero in large portions of space.

About Perlin's "Simplex" Noise

  • Perlin's "Classic" Noise (1984) is an algorithm producing pseudo-random fluctuations simulating natural looking variations, producing paterns all of the same size. It is a kind of gradiant-noise algorithm, invented by Ken Perlin while working on visual special effects for the Tron movie (1982). It works by interpolating pseudo-random gradiants defined in a multi-dimensionnal grid. Ken Perlin original references
  • Perlin's "Improved" Noise (2002) switches to a new interpolation fonction with a 2nd derivative zero at t=0 and t=1 to remove artifacts on integer values, and switches to using predefined gradients of unit lenght to the middle of each edges. Ken Perlin original references
  • Perlin's "Simplex" Noise (2001) rather than placing each input point into a cubic grid, based on the integer parts of its (x,y,z) coordinate values, placed them onto a simplicial grid (think triangles instead of squares, pyramids instead of cubes...) Ken Perlin original references

Fractional Brownian Motion

Fractional Brownian Motion is the summation of successive octaves of noise, each with higher frequency and lower amplitude. It doesn't especially matter what kind of noise, most will do. Fractional Brownian Motion (often abbreviated as fBm) is often confused with noise algorithms like Value Noise and Perlin Noise, when in fact it just takes a type of noise and makes a more interesting picture.

function fBm(x, y, octaves, amplitude, frequency, persistence, lacunarity) {
    let total = 0;

    for (let i = 0; i < octaves; i++) {
        total += noise(x * frequency, y * frequency) * amplitude;
        frequency *= lacunarity;
        amplitude *= persistence;
    }

    return total;
}

There are five terms here: octaves, amplitude, frequency, lacunarity, and persistence.

Octaves are how many layers you are putting together. If you start with big features, the number of octaves determines how detailed the map will look.

The amplitude is how tall the features should be. Frequency determines the width of features, amplitude determines the height. Each octave the amplitude shrinks, meaning small features are also short. This doesn't have to be the case, but for this case it makes pleasing maps.

The frequency of a layer is how many points fit into the space you've created. So for the mountain scale, you only need a few points, but at the rock scale you may need hundreds of points. In the code above, I start with a small frequency (which equates to large features) and move to higher frequencies which produce smaller details.

Persistence, also called gain, is what makes the amplitude shrink (or not shrink). Each octave the amplitude is multiplied by the gain. I use a gain of 0.65. If it is higher then the amplitude will barely shrink, and maps get crazy. Too low and the details become miniscule, and the map looks washed out. However, most use 1/lacunarity. Since the standard for lacunarity is 2.0, the standard for the gain is 0.5. Noise that has a gain of 0.5 and a lacunarity of 2.0 is referred to as 1/f noise, and is the industry standard.

Lacunarity is what makes the frequency grow. Each octave the frequency is multiplied by the lacunarity. I use a lacunarity of 2.0, however values of 1.8715 or 2.1042 can help to reduce artifacts in some algorithms. A lacunarity of 2.0 means that the frequency doubles each octave, so if the first octave had 3 points the second would have 6, then 12, then 24, etc. This is used almost exclusively, partly because octaves in music double in frequency. Other values are perfectly acceptable, but the results will vary.

SplitMix32

SplitMix32 is a transformation of the fmix32 finalizer from MurmurHash3 into a PRNG. It has a 32-bit internal state, like Xorshift and Mulberry32.

function splitmix32(a) {
    return function() {
      a |= 0; a = a + 0x9e3779b9 | 0;
      var t = a ^ a >>> 16; t = Math.imul(t, 0x21f0aaad);
          t = t ^ t >>> 15; t = Math.imul(t, 0x735a2d97);
      return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
    }
}

This is based on an algorithm known as SplitMix included in Java JDK8. It uses 64-bit arithmetic and doesn't define a 32-bit version. However, It is derived from the fmix64 finalizer used in MurmurHash3 and appears to be an application of Weyl sequences. MurmurHash3 also contains a 32-bit equivalent of this function, fmix32. The constant 0x9e3779b is the 32-bit truncation of the golden ratio, which is also what is used in the original.

References