2D Perlin Noise

What is Perlin noise?

Perlin noise is a type of procedural noise developed by Ken Perlin. It's commonly used to create organic-looking surfaces, or displacement/bump to make surfaces appear more natural and varied. Common uses include fire, clouds, water and terrain. In NPR, this can be used to introduce imperfections into elements such as shading to reduce the appearance of being CGI.

From Wikipedia's page on Perlin noise:

Perlin noise is most commonly implemented as a two-, three- or four-dimensional function, but can be defined for any number of dimensions. An implementation typically involves three steps: defining a grid of random gradient vectors, computing the dot product between the gradient vectors and their offsets, and interpolation between these values.

Define an n-dimensional grid where each grid intersection has associated with it a fixed random n-dimensional unit-length gradient vector, except in the one dimensional case where the gradients are random scalars between -1 and 1.

For each corner, we take the dot product between its gradient vector and the offset vector to the candidate point. This dot product will be zero if the candidate point is exactly at the grid corner.

Simple Implementation

Following Wikipedia's example implementation, this requires three functions.

  1. RandomGradient. This function returns a random number.

  2. DotGridGradient. This returns the dot product of the distance and gradient vectors.

  3. The Perlin function itself, which calculates the noise at the x and y coordindates.

Here is a simple implementation for use in Malt.

#include "Pipelines/NPR_Pipeline.glsl"

//Perlin noise functions adapted from https://en.wikipedia.org/wiki/Perlin_noise#Implementation by Riya.

float interpolate (float a, float b, float t)
{
    return a + (t * (b - a));
}

vec2 random_gradient(int ix, int iy)
{
    //change the first number to make it seem to churn/flow
    float random = 2000 * sin(ix * 21942 + iy * 171324 + 8912) * cos(ix * 23157 * iy * 217832 + 9758);
    vec2 returner = vec2(cos(random), sin(random));
    return returner;
}

float dot_grid_gradient(int ix, int iy, float x, float y)
{
    vec2 gradient = random_gradient(ix, iy);

    float x_distance = x - ix;
    float y_distance = y - iy;
    return ((x_distance*gradient.x) + (y_distance*gradient.y));
}

float perlin_2d(float x, float y, float scale)
{

    x = x*scale;
    y = y*scale;
    int XLeft = int(x);
    int XRight = XLeft+1;

    int YBottom = int(y);
    int YTop = YBottom+1;

    float SmoothX = x - XLeft;
    float SmoothY = y - YBottom;
    
    float n0 = dot_grid_gradient(XLeft, YBottom, x, y);
    float n1 = dot_grid_gradient(XRight,YBottom,x,y);

    float interpolated_x = interpolate(n0,n1, SmoothX);

    n0 = dot_grid_gradient(XLeft,YTop,x,y);
    n1 = dot_grid_gradient(XRight,YTop,x,y);

    float interpolated_y = interpolate(n0,n1,SmoothX);

    return interpolate(interpolated_x,interpolated_y,SmoothY);
}

Actual Use

An example of what this could be used for is to displace the mesh on screen to take away its perfect CGI edge. This code will be referencing from the basic_displacement example.

"#define CUSTOM_VERTEX_DISPLACEMENT" must be before the pipeline.

#define CUSTOM_VERTEX_DISPLACEMENT
#include "Pipelines/NPR_Pipeline.glsl"

Add the following variables, for better control.

uniform float noise_scale = 50;
uniform float displacement_strength = 1;

Displacement strength defaults to 1, but should be at a low value for this. Noise_scale can be anything, but lower values will make more subtle changes. Uniform allows them to be viewed in the Material Settings. Then, add this function. It uses the screen's UV and applies the Perlin to it. By doing it this way, it can emulate imperfection in a 2d drawing that has no real depth, no Z axis to be distorted on.

vec3 VERTEX_DISPLACEMENT_SHADER(Surface S)
{
    vec3 displacement = vec3(0,0,0);
    vec2 screen = screen_uv();
    float perlin = perlin_2d(screen.x,screen.y,noise_scale);
    displacement = vec3(perlin,perlin,0)*displacement_strength;
    return displacement;
}

Add this for the colour and shading.

void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)
{
    vec3 diffuse = vec3(0,1,0) * get_diffuse_half();
    PO.color.rgb = diffuse;
}

The result should look like this.

Conclusion

Perlin noise is a useful function. There are many ways to take advantage of it; the use here is only a very basic way, with 2D Perlin. There are many uses, and variations on Perlin noise to look into. They're well worth consideration for their many applications in NPR.

Last updated