Procedural Noise

From Shader Forge Wiki
Jump to: navigation, search

Intro

A core component of computer graphics, noise functions offer powerful advantages over traditional texture maps, including:

  • Infinite resolution - zoom in or out without artifacts such as pixelation or blurriness
  • Computed at the bit precision of the graphics hardware. In most cases, this will be 32 bits per pixel, as opposed to the 8 bits you get from an uncompressed texture, or 4/5 bits you get from a DDS compressed texture.
  • Fully animatable
  • They occupy no storage space on your RAM, VRAM or HDD/SSD

Noise functions can be used in computer graphics for a nearly endless number of uses. On a GPU, you should consider noise functions where texture resolution or bit depth is problematic, or where you want endless/cheap variation. A common technique used in effects shaders is to use one texture to distort the UV lookups of another (water, fire, etc); noise can have particular advantages here, as bit depth and texture compression look particularly bad in these shaders.


Generating Noise

Here is an example of two copies of a noise function, with one noise distorting the other noise.

Sf valuenoise.jpg

While there are multiple techniques to generate procedural noise textures available, they all follow a basic high level principal. First, you generate several pseudo random values for several interpolation points around the pixel being processed, then you interpolate between these values to produce the value for the current pixel.

The first challenge on a GPU is producing the pseudo random numbers; because a GPU has no rand() function. This is actually a deep and complex topic, better covered by some of the referenced material below, but the simplest version (used in this example) uses the sin() function with a high enough frequency that it's results begin to look random.

To generate noise for the current pixel, we need to determine a standard set of points around the pixel, so that the pixels near us will use the same set of points and random numbers for their value. For this, we use the Floor node to give us the integer component of our UV coordinate, and the frac node to give us the fractional part. This if our UV coordinate is 0.5, 1.6, our floor would be 0, 1 and the frac would be 0.5, 0.6.

So, we generate our pseudo random numbers using the floor(uv), floor(uv) + 0,1, floor(uv) + 1,1, and floor(uv) + 1,0. This gives the pseudo random values for a square around out pixel. Then we can lerp between these results with our fractional component to produce a smooth gradient between the values!

If you've reconstructed the shader above to this point, you will have some very blocky looking noise - this is because Lerp provides linear interpolation, which doesn't look that great. What we want is a non-linear interpolation, such as a hermite curve. Luckily, this is pretty easy to add; simply take the fractional part (f) and perform a hermite function on it using the formula f = f * f * (3 - 2 * f);

This will give you an aesthetically pleasing noise.

Performance

Performance will obviously depend on the platform and scene being drawn, but on a PC computing a single noise function entirely in the shader is between 1.4 and 2 times as slow as using a texture. Note that you can speed this up considerably by using more advance hashing (random) functions, or by replacing the random function with a texture lookup into a specially constructed texture (where the value in each texture channel is equal to the result of one of our four random values).


And here is the final shader in case you'd like to play with it; be warned, it can take a while to load. File:ValueNoise.shader

References

Some wonderful resources on Noise are included for reference:

http://briansharpe.wordpress.com/

http://iquilezles.org/www/articles/warp/warp.htm

http://thndl.com/?15

Using the Code node

Inputs: UV float2

It takes a UV (float2) as input. To change the frequency of the noise, scale (multiply) the incoming UV coordinate. You can adjust the amplitude of the noise by multiplying the output.

  float2 p = floor(UV);
  float2 f = frac(UV);
  f = f * f * (3.0 - 2.0 * f);
  float n = p.x + p.y * 57.0;
  float4 noise = float4(n, n + 1, n + 57.0, n + 58.0);
  noise = frac(sin(noise)*437.585453);
  return lerp(lerp(noise.x, noise.y, f.x), lerp(noise.z, noise.w, f.x), f.y); 

Noise 1octave.png


Here is an example of the noise function used in an Fractional Brownian Motion function. An FBM function simply computes several octaves of noise and adds them together, each with reduced amplitude and increased frequency. This produces a noise function similar to the one you see in Photoshop's "Clouds" filter.

  float ret = 0;
  int iterations = 6;
  for (int i = 0; i < iterations; ++i)
  {
     float2 p = floor(UV * (i+1));
     float2 f = frac(UV * (i+1));
     f = f * f * (3.0 - 2.0 * f);
     float n = p.x + p.y * 57.0;
     float4 noise = float4(n, n + 1, n + 57.0, n + 58.0);
     noise = frac(sin(noise)*437.585453);
     ret += lerp(lerp(noise.x, noise.y, f.x), lerp(noise.z, noise.w, f.x), f.y) * ( iterations / (i+1));
  }
  return ret/iterations;


Noise 6octave.png

Note that 6 octaves of noise is pretty expensive to compute on the GPU, but even a single octave of noise can be incredibly useful.

--Jason Booth (talk) 04:47, 23 March 2014 (UTC)