Nothing Precedes Matter

npm

atlas-vector-noise

1.0.0 • Public • Published

atlas-vector-noise

An implementation of 2D perlin noise, used to generate more natural-looking noise compared to value noise.

install

``````npm install --save atlas-vector-noise
``````

why

Value noise uses randomly fixed scalars on a lattice to generate interpolated noise values for points in between the lattice points.

Perlin noise uses a vector field instead, specifying not the value of the noise at the lattice points, but the gradient (i.e. how the noise changes at the lattice points). Perlin noise was invented by Ken Perlin in 1983 because value noise tends to be blocky and looks very digital. Perlin noise looks much more "natural" and its features tend to adhere to a desired frequency.

I suggest reading the docs for atlas-scalar-noise before continuing in this section, since Perlin noise builds upon value noise. If you look at the source code for both of these modules, you'll find that Perlin noise and value noise have very similar implentations.

There's only one big difference between the two implementations. With value noise, our grid points are scalars, so we just skip right to interpolation. With Perlin noise, our grid points are vectors, meaning we have to turn them into scalars before we do the interpolation step. Perlin noise is pretty simple: it uses dot products between the grid vectors and the relative position vectors to derive the desired scalars.

examples

generating a grid

Perlin noise requires a random grid to work. The grid defines random gradients at various points in your image. All of the values in between are implied with linear interpolation and quintic smoothing. Smoothing helps remove unwanted artifacts in the output image.

rectangular grid

We can generate pseudorandom rectangular grids:

square grid

We can also generate square grids:

generating noise

Once we've generated our grid, we can use that grid to generate noise. The grid defines an `NxM` lattice of pseudorandom vectors. We can get pixels at the grid lattice points, and/or between them if we want. For the rest of these examples, let's assume we have a `Canvas` API which lets us generate an empty canvas, set pixels on it and print the resultant image. We'll use the square grid we created in the example above for simplicity, but feel free to play around with rectangular grids, too.

regular noise?

Let's try getting pixels at exactly the grid lattice points and see what happens:

Unlike value noise, Perlin noise will not generate random white noise if you are only calling `squareGrid.getPixel` at the grid's lattice points. If you look into the Perlin noise implementation, this makes sense because all of the relative position vectors collapse to zero at the lattice points, and any vector multiplied by the zero vector is zero.

Okay, so if it should be zero then why are we seeing solid grey? Normally, Perlin noise is distributed in the range `[-1, 1]`, however in this implementation, we transform the output such that it is the range: `[0, 1]`. Thus an output of `0` in the classical implementation would result in an output of `.5` in our implemention -- which would become roughly `rgb(127,127,127)` on the greyscale in the example above. This is why our output image is a solid grey.

perlin noise

We can use the grid to generate pixels in between the lattice points, giving us proper Perlin noise. In order to get pixels in between the lattice points, we need to increment our `x` and `y` values by amounts less than one. We can divide our `x` and `y` values by a scale factor. A scale factor of `4` tells the generator that we are interested in interpolating 3 brand new values in between every grid point. The `x` values will be: `[0, .25, .5, .75, 1, ..., 99, 99.25, 99.5, 99.75]` for a total of 400 values. This is also true for the `y` values, since our grid is a square.

As you can see, we've just created some high-frequency Perlin noise. If you compare it to value noise of the same frequency, you'll notice it looks more rounded and less blocky, kind of like little worm-holes (not the gravitational ones) or slug tracks or something.

better perlin noise

The reason why the example above still looks pretty much like white noise is that we aren't interpolating enough values in between our grid points. The more we interpolate, the smoother the output image will look. In this example, we will use a smaller grid and interpolate more values in between the lattice points, while keeping our canvas a square of size `400`.

That looks more like it! Since our scale factor is larger and we divide our `x` and `y` values by it, more points will be generated in between each grid point. Namely, there will be 39 interpolation points between every grid lattice point, as opposed to only 3 in the previous example. For example, our new `x` values are: `[0, .025, .05, ..., 9.925, 9.95, 9.975]` for a grand total of 400 values. This is also true for the `y` values, since our grid is a square.

As you can see, our features are much larger and it looks much smoother. I've always thought that Perlin noise looked kind of like a blurry sattelite photo of rolling hills.

infinite perlin noise

You might we wondering what happens if we try to generate noise for `x` and `y` values which are not inside of our grid. For example, if we have a grid of size `10x10`, what happens if we try `grid.getPixel(11,11)`? This is outside of our grid, so will it work? Let's try it using our small square grid with a smaller scale factor:

It took me 20+ iterations to get the image above. I was trying to get an image that had obvious repetition. One thing to note about Perlin noise is that the features tend to be much more regular than those in value noise, so it's harder to notice patterns.

Can you find the feature that looks like a six-pack? Notice how the six-pack feature repeats itself in both dimensions. This is because `VectorNoiseGenerator` implements periodic boundary conditions -- if you try and reach for a pixel that isn't inside the grid's boundary, it will "wrap around" and get the value from the beginning of the grid. To answer the question above, if we have a grid size of `10x10` and try `grid.getPixel(11,11)`, we would really just be getting the value of `grid.getPixel(1,1)`.

To understand why this example tesselated 4 times in each dimension, we need to look at our scale factor. It was reduced from `40` to `10` without changing our canvas size, meaning the `x` values become: `[0, .1, .2, ..., 39.7, 39.8, 39.9]` for a total of 400 points. This is also true for the `y` values, since our grid is a square. Notice how we almost go up to `40` in both dimensions -- meaning we are "wrapping" around our `10x10` grid three extra times.

This tesselation is useful when you want to indefinitely continue noise in some direction.

fractal perlin noise

Now that we understand how to generate Perlin noise, let's create a gas cloud. Fractal noise is where you reduce contributions from higher frequency signals according to a power law, as opposed to white noise which has a uniform contribution from all frequencies:

This is what pink Perlin noise looks like -- if you compare it to the example in atlas-scalar-noise, it looks very similar. However, after many interations, you start to realize that Perlin noise tends to produce less artifacts than value noise, and tends to look smoother and slightly more natural than value noise.

mountain ranges

In our final example, we'll use Perlin noise to generate a picture which looks a lot like a bird's eye view of a rocky mountain range:

The first thing to note is how we're adding different "features" together. You can think of Perlin noise as having a certain frequency, depending on how large the underlying grid is and how many points you interpolate between each lattice point. When you are thinking about how to replicate something in the real world with Perlin noise, it helps to break down the real-world example as a sum of different frequency components. For example, mountain ranges tend to have large features like hills and smaller features like jagged rocks and gravel. Many functions can be decomposed as a sum of frequencies, thaks to Fourier decomposition.

You might notice how we added and subtracted equal amounts of `hills`, `rocks`, and `weather`. This was done to add a ruggedness to the texture of the mountain range. If we were to only add these components, the range would look less rocky and more smooth. The `mountains` grid has a 1:1 aspect ratio so that it stretches by a factor of `2.5` when rendered on a `1000x400` canvas, making it feel more like a long, contiguous mountain range than just a random assortment of bumps. Take a look at the larger valleys and peaks and notice how they tend to continue for a while on the horizontal axis:

This "mountain" example is just the beginning! Go ahead and Google "perlin noise" to see all the kinds of awesome stuff you can generate with Perlin noise.

caveats

optimization

Perlin noise can be optimized by limiting the possible gradient vectors and doing clever bitwise operations. This module does not implement any of these optimizations. Doing these optimizations in javascript might be an act of futility, anyway. This module is mainly for educational purposes and fun.

distribution

By default, Perlin noise is non-uniformly distributed in `[-1, 1]`. The Perlin noise output from this module is incremented by one and then halved so that it lies in `[0, 1]`. If your images don't have enough values near the extrema, it's because Perlin noise tends to be closer to the mean value (`.5` in this module); very rarely do you get values close to the ends of the interval. You can manually restrict the boundary to `[.2, .8]` if you want to see more noise values closer to the endpoints.

Install

`npm i atlas-vector-noise`

10

1.0.0

Apache-2.0

214 kB

11