Noise in Computer Graphics- A brief introduction

Using a Noise function in shaders can produce quite interesting visual effects. For example, you can simulate clouds, fire, wood, etc. In this post I will give you a brief introduction to Noise and give you a couple of examples of using noise with shaders.

What is a Noise Function?

A noise function is a real-valued function that varies between 0 & 1 over some domain. The noise function is generated by determining a Pseudo-Random Number (PRN) in the domain.

  • If the domain is an interval, you will get a 1-Dimensional noise.
  • If the domain is a plane, your noise will be 2-Dimensional.
  • If the domain is a 3D space, your result will be a 3D noise.

The important point to remember is that the output of the noise function can be used to modify the pixels in the fragment shader.

Types of noise

There are three types of noise which you need to be aware of. They are:

  • Value Noise
  • Gradient Noise
  • Value+Gradient Noise

Value Noise

In Value Noise, the PRN is used as the noise function input and the slope of the function at each point in the interval is set to zero.

Gradient Noise

In Gradient Noise, each point in the interval is used as the noise function input and the PRN values at that fixed point are used as the slope (gradient) of the curve.

You may have heard of Perlin Noise. This type of noise is based on PRN gradients.

Noise Concepts

There are several Noise Concepts which you should be aware. I will talk about:

  • Fractional Brownian Noise
  • Turbulance

Fractional Brownian Noise

Fractional Brownian Noise, also known as 1/f, models an operation that has many different frequencies and magnitudes. Basically, for each noise function defined at different frequencies, the magnitude of the noise is divided by the frequency multiplier. The multiplier is a power of 2. So the multipliers used are 2, 4, 8, 16, etc.

Turbulance

Turbulance is created from the noise function. More specifically it is created by taking the absolute value of each noise Octave about the midpoint before summing them.

Each frequency doubling is called an Octave.

Noise Examples

I'm going to show you two examples of noise using the OpenGL Shading Language. To make it simple to understand I will be using a Shader Editor which was developed in WebGL.

I will show you how to create a Marbel noise effect on a sphere.

Using Shdr Editor

The Shader Editor is quite simple to use. Below is an image of the shader. You can select which shader you want to work with: Vertex or Fragment shader and also which object you want to render.

Figure 1. Shdr Editor

Shdr Editor

You can change between shaders by clicking on the shader button as shown below:

Figure 2. Selecting the shader

Shdr Shader

Changing between objects is done by clicking on the right-top side of the editor and selecting your object.

Figure 2. Selecting the object

Shdr Object

For this exercise select the Sphere.

Noise Function

Unfortunately, the WebGL API does not have a Noise function. Fortunately, some very nice people have implemented Noise functions that can be used in shaders. A very nice collection of Noise functions can be found here.

I will be using the float snoise(vec3 v) Noise Function which can be found in the src folder under this file

Below is a listing of the function.

Listing 1. Noise function

vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289(vec4 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x) {
     return mod289(((x*34.0)+1.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

float snoise(vec3 v)
  { 
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);

// First corner
  vec3 i  = floor(v + dot(v, C.yyy) );
  vec3 x0 =   v - i + dot(i, C.xxx) ;

// Other corners
  vec3 g = step(x0.yzx, x0.xyz);
  vec3 l = 1.0 - g;
  vec3 i1 = min( g.xyz, l.zxy );
  vec3 i2 = max( g.xyz, l.zxy );

  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
  //   x1 = x0 - i1  + 1.0 * C.xxx;
  //   x2 = x0 - i2  + 2.0 * C.xxx;
  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
  vec3 x1 = x0 - i1 + C.xxx;
  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y

// Permutations
  i = mod289(i); 
  vec4 p = permute( permute( permute( 
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  float n_ = 0.142857142857; // 1.0/7.0
  vec3  ns = n_ * D.wyz - D.xzx;

  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

  vec4 x_ = floor(j * ns.z);
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  vec4 x = x_ *ns.x + ns.yyyy;
  vec4 y = y_ *ns.x + ns.yyyy;
  vec4 h = 1.0 - abs(x) - abs(y);

  vec4 b0 = vec4( x.xy, y.xy );
  vec4 b1 = vec4( x.zw, y.zw );

  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
  vec4 s0 = floor(b0)*2.0 + 1.0;
  vec4 s1 = floor(b1)*2.0 + 1.0;
  vec4 sh = -step(h, vec4(0.0));

  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  vec3 p0 = vec3(a0.xy,h.x);
  vec3 p1 = vec3(a0.zw,h.y);
  vec3 p2 = vec3(a1.xy,h.z);
  vec3 p3 = vec3(a1.zw,h.w);

//Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;

// Mix final noise value
  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
                                dot(p2,x2), dot(p3,x3) ) );
  }

Marble Noise

Let's create a texture that ressembles a Marble as shown below.

Figure 3

Marble Noise

In the Shdr Editor, switch to the Vertex shader and copy the following:

Listing 2. Marble Vertex Shader

precision highp float;
attribute vec3 position;
attribute vec3 normal;
uniform mat3 normalMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
varying vec3 fNormal;
varying vec3 fPosition;

void main()
{

  fNormal = normalize(normalMatrix * normal);

  vec4 pos = modelViewMatrix * vec4(position, 1.0);

  fPosition = position.xyz;

  gl_Position = projectionMatrix * pos;
}


The Vertex Shader above, simply tranforms the vertices into the Model-View Space and provides the output to the gl_Position.

A varying variable fPosition gets assigned the vertices in Model-Space.

Switch over to the fragment shader and copy the noise function in listing 1. Also copy the following:

Listing 2. Marble Fragment Shader

precision highp float;
uniform float time;
uniform vec2 resolution;
varying vec3 fPosition;
varying vec3 fNormal;

//...Noise function here...

void main()
{
  //1. set red color for sphere
  vec3 color1=vec3(1.0,0.0,0.0);
  //set white color for marble effect
  vec3 color2=vec3(1.0,1.0,1.0);

  //2. calculates the noise
  vec4 noise=vec4(snoise(fPosition),snoise(fPosition+17.0),snoise(fPosition-43.0),snoise(fPosition+64.));

  //3. calculates the turbulance
  float sum=abs(noise.x-0.5)+abs(noise.y-0.5)+abs(noise.z-0.5)+abs(noise.a-0.5);

  //4. clamp the noise to 0-1
  sum=clamp(10.0*sum,0.0,1.0);

  //5. calculate the mixing factor
  float sieval=sin(fPosition.x*6.0+sum*122.0)*0.5+0.5;

  //6. Mix the two colors
  vec3 color=mix(color1,color2,sieval)*1.0;

  //7. assigns the color
  gl_FragColor = vec4(color,1.0);
}

Let's go through the whole shader.

In line 1, I set up two different colors which will be mixed depending on the noise factor.

In line 2, the noise is calculated. If you take a look at the noise function, it returns a float value. In line 2, I create a vec4 variable and each element receives a noise value. You may also notice that each noise function receives the position vertex plus an offset. This offset is required as mentioned by the author of the noise function.

In line 3, each element of the noise is subtracted by 0.5 and the result is then added into a float variable called sum. This step resembles the calculation of Turbulance as mentioned above.

In line 5, we calculate the mixing noise factor which is then used to blend in the two colors (line 6).

If you play around with this value, you will see how the blending noise is affected.

Below you see the noise generated with the fragment shader. It produces that marble effect that it is well known.

Figure 4. Final Result

Marble Noise

If you are interested in learning more about Noise, I recommend to read this book.

Harold Serrano

Computer Graphics Enthusiast. Currently developing a 3D Game Engine.