Skip to content

Using MetalKit part 14

Let’s pick up where we left off in Part 13. Using the same playground we worked on last time, we will learn about noise today. From Wikipedia:

Noise refers to any random fluctuations of data that makes the perception of an expected signal, difficult. Value noise is a type of noise commonly used as a procedural texture primitive in computer graphics. This method consists of the creation of a lattice of points which are assigned random values. The noise function then returns the interpolated number based on the values of the surrounding lattice points. Multiple octaves of this noise can be generated and then summed together in order to create a form of fractal noise.

The most obvious characteristic of noise is randomness. Since MSL does not provide a random function let’s just create one ourselves. What we need is a random number between [0, 1]. We can get that by using the fract function which returns the fractional component of a number:

float random(float2 p)
    return fract(sin(dot(p, float2(15.79, 81.93)) * 45678.9123));

The noise() function will bilinearly interpolate a lattice (grid) and return a smoothed value. Bilinear interpolation allows us to transform our 1D random function to a value based on a 2D grid:

float noise(float2 p)
    float2 i = floor(p);
    float2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float bottom = mix(random(i + float2(0)), random(i + float2(1.0, 0.0)), f.x);
    float top = mix(random(i + float2(0.0, 1.0)), random(i + float2(1)), f.x);
    float t = mix(bottom, top, f.y);
    return t;

We first use i to move along grid points and f as an offset between the grid points. Then we calculate a Cubic Hermite Spline with the formula 3f^2 - 2f^3 and which creates a S-shaped curve that has values between [0, 1]. Next we interpolate values along the bottom and top of the grid, and finally we interpolate the vertical line between those 2 horizontal points to get our final value for noise.

Next we create a Fractional Brownian Motion function that calls our noise() function multiple times and adds up the results.

float fbm(float2 uv)
    float sum = 0;
    float amp = 0.7;
    for(int i = 0; i < 4; ++i)
        sum += noise(uv) * amp;
        uv += uv * 1.2;
        amp *= 0.4;
    return sum;

By adding various (four – in this case) octaves of this noise at different amplitudes (as explained in the beginning), we can generate a simple cloud-like pattern. We have one more thing to do: inside the kernel replace all the lines after the one where distance is defined, with these lines:

uv = fmod(uv + float2(timer * 0.2, 0), float2(width, height));
float t = fbm( uv * 3 );
output.write(distance < 0 ? float4(float3(t), 1) : float4(0), gid);

For fun, we add the timer uniform again to animate the content. The output image should look like this:

alt text

It looks cool, but to make it look more realistic we need to learn how to apply a texture. You can read more about bilinear filtering, about value noise and about Fractional Brownian motion if you’re interested. You can also see an example of the Cubic Hermit Spline. The source code is posted on Github as usual.

Until next time!

Leave a Reply

Your email address will not be published. Required fields are marked *