Procedural Generation

Coherence and Degredation of Noise

Coherence

The concept of coherence is important to generating textures that are aesthetically pleasing. Without coherence, the texture overall will look noisy and random, rather than smooth and natural. Coherence gives the generated image some form of structure. The output from the raw hash function is not coherent, but we create a different process to generate coherent noise from the raw noise.

Below, there are some boxes that have procedurally generated textures in them. The left of these boxes has 'raw' noise in it. The other two boxes to the right are two different filters applied onto the same noise field, to generate coherent noise. When zoomed all of the way 'out', these fields will all look similar, but when zoomed in, the fields have their own characteristics.

Comparison of 'raw' noise and 'coherent' noise
Interactive; Click and drag to move the noise fields!

If you want a version of this that can run on your graphics card, and is much more responsive, go here or here.

Some stuff to note:

  1. At any scale, the raw noise looks roughly the same- like old TV static.
  2. At Integer Positions, when zoomed all the way out, all 3 textures look roughly the same
  3. Many features are present in both the Interpolated Noise and the Smoothed Interpolated Noise at any scale
  4. It's SLOW- We're running on the CPU, we need more POWER! (Next time, we will run on the GPU!)

Interpolated noises create a field that is coherent, even at non-integer offsets. the noise field generated by the raw function has wildly different values at possible point.

The 'Raw Noise' block is generated by this function, applying using the hash function from the previous page:

(NVIDIA CG)
float hash2d(float x, float y) { 
	return hash(x + 1333.0 * y);
}

Interpolation
We then take the output from that 2d hash function, and 'Interpolate' it. We sample the function only at fixed points- In this case, integer values for x and y, and then 'Interpolate' between the nearest sample points to determine the value at any given point. Interpolation takes data that is known, and then creates more data between the known data. The most well known interpolation function is Linear Interpolation, or lerp for short.

(NVIDIA CG)
float lerp(float a, float b, float x) { return a + (b-a) * x; }

This function is very commonly used in all sorts of applications. It can be used for animation, for smoothing images, mixing colors, and anything else that can be done by taking two points, and moving to some position between them. There are also variations on this idea, that interpolate between values in a more smooth fashion.

(NVIDIA CG)
float coserp(float a, float b, float x) { 
	float ft = x * 3.1415927;
	float f = (1 - cos(ft)) * .5;
	return a + (b-a) * f;
}

float smoothstep(float a, float b, float x) {
	float t = saturate((x - a)/(b - a));
	return t*t*(3.0 - (2.0*t));
}
Note: the saturate function clamps a value between 0 and 1.
Comparison of Different Interpolation Functions
For all of these graphs, a is 0, and b is 1. The vertical represents the inputs 'a' and 'b', as well as the output of the function. The horizontal represents the input 'f'. Also, all of these interpolation methods not only work for scalars, but also on vectors of any length.
The inputs to all of these functions is (0, 1, .65)
.
Linear
lerp(a, b, f)
Cosine
coserp(a, b, f)
'Hermite'
smoothStep(a, b, f)
f (position) 0 1 out a b
f (position) 0 1 out a b
f (position) 0 1 out a b

As can be seen from the above graphs, lerp is just a line between two points. Cosine interpolation and 'smoothStep' look roughly the same, but have slightly different outputs. The difference is in their implementations. Depending on how the cosine function is implemented in the environment (lookup table, crunching a polynomial, or an actual evaluation), the performance of the function may change. Typically the 'smoothstep' interpolation function is used.

The function can then be applied to interpolate between output of a hash function at given points.
Consider the following code:

(NVIDIA CG)
//Coherent, 2d noise function
float noise2d(float2 v) {
	float ix = floor(v.x);   //Integer X
	float fx = v.x-ix;       //Fractional X
	float iy = floor(v.y);   //Integer Y
	float fy = v.y-iy;       //Fractional Y
	
	float v1 = hash2d(ix  , iy  ); //Sample 
	float v2 = hash2d(ix+1, iy  ); //4 integer points
	float v3 = hash2d(ix  , iy+1); //around (x,y)
	float v4 = hash2d(ix+1, iy+1);
	
	float i1 = smoothstep(v1, v2, fx); //Interpolate across x axis
	float i2 = smoothstep(v3, v4, fx); 
	return smoothstep(i1, i2, fy);     //Interpolate across y axis
}

This is a 2-dimensional interpolation. We sample 4 points (at integer positions). Then we apply an interpolation function between the sampled values, and the fractional position of the target.

Here is a graphical representation of what this looks like.

Interpolation
x y v1 v2 v3 v4 i1 i2 (x,y) ix iy 1 1 fx fy

All of these features together finally give us a nice coherent noise function. Here are the properties that any function would need to be coherent noise:

Coherent Noise: Definition

A type of smooth, pseudorandom noise, generated by a coherent-noise function, with 3 main properties:

  1. Passing in the same input will result in the same output.
  2. A small change to the input will result in a small change to the output.
  3. A large change to the input will result in a unpredictable change to the output.
Taken from libnoise's documentation

This is just one of many possible noise functions- Coherent noise can be created in a few different ways. Depending on how the noise function is constructed, the resulting noise can look very different. There are also ways to create noise that doesn't degrade as quickly due to floating point precision loss, such as spiral noise (that makes heavy use of sine and cosine functions at different phases).

Depending on the construction of the noise, it can have a different look, tiling period, and speed of calculation. It may also degrade at a different rate, and need to be looked at at different scales. Here is a comparison of some of the looks and degredation patterns of different kinds of noise.

Screenshots from various noises, their field images, and various edges (if applicable)
Our Noise
Stable
Zoom Factor of 128 around (0,0)
Noise Field
Zoom Factor of 628000 around (0,0)
Middle Edge
Zoom Factor of 128 around (0,48111)
Far Edge
Zoom Factor of 128 around (0,192350)
Notes:
Scale factor is quite large 128, and low detail. This is just a single layer of noise. Most of the others in this section are 'Fractal Noise' (Discussed in the second 'paper'). Also, for most of these, exact positions of edges may be different for different hardware.
'Voronoise'
Stable
Zoom Factor of 1 around (0,0)
Noise Field
Zoom Factor of 10000 around (0,0)
Middle Edge
Zoom Factor of 1, around (411.5, 411.5)
Far Edge
Zoom Factor of 1, around (823, 823)
Notes:
This gives more detail, and a more interesting appearance. The noise 'primitive' that is used, is also a type of noise that is based on voroni distance and blur filters, but is built on the same sort of noise that ours is.
'Convergent Noise'
Stable
Zoom Factor of 15 around (0,0)
Noise Field
Zoom Factor of 5333 around (0,0)
Middle Edge
Zoom Factor of 200, around (2595, 20600)
Far Edge
Zoom Factor of 200, around (17655, 6250)
Notes:
This noise is based on converging number series. An interesting implementation of a noise function, and the noise layers that are added are rotated from each other to mask the period of the noise.
'Elastic Noise'
Stable
Zoom Factor of 10 around (0,0)
Noise Field
Zoom Factor of 300000000 around (0,0)
Middle Region
Zoom Factor of 100, around (0, 26353580)
Far Region
Zoom Factor of 100, around (26353600, 26353600)
Notes:
This version didn't come with any documentation comments, so I came up with the name myself. The noise is constructed from many interlaced sin/cos functions based on the x and y positions of the points. It doesn't degrade evenly, hence the odd cross pattern in the noise field. It also has an absolutely massive noise field, but still suffers from degredation as early as 10000 units away from the origin, like most other functions.
'Flow Noise'
Stable
Zoom Factor of 10 around (0,0)
Noise Field
Zoom Factor of 20000000 around (0,0)
Distant Region
Zoom Factor of 2, around (-180000, 80000)
Distant Region
Zoom Factor of 2, around (80000, 0)
Notes:
This is a form of simplex noise, in this case, using a 3d simplex. (Tetrahedron). Similarly to how we did interpolation on the verticies of a square in 2d space, simplex noise does interpolation on the verticies of a simplex in whatever space it's in, for example, a 2d simplex noise would use triangles.

Degredation of noise happens for many reasons, but is present because graphics hardware is just not powerful enough to calculate noise functions when the input positions get too far from the origin. The typical culprit is floating point rounding errors. This happens earlier with processes that use both large and small numbers together, but eventually happens to every noise function. There are ways to prevent, or minimize the amount of degredation present, but degredation is pretty much a given for any noise function. The specifics of why and how noise degrades depends on the specifics of the implementation of the noise, and the hardware it is run on.