Noise Part II (CPU)

Well, here is the second part of the noise articles. In this article, I’ll first post the 3d noise code which I have started to use to produce my 3D perlin height map textures, and then I’ll post some functions for using the perlin noise The results can be used for a handful of things, including height maps, procedural textures and 3d textures (for skyboxes etc.).

In order to actually use a Perlin noise function, we need a pseudo-random number generator. In the following code, an array is create in the function Init() which produces a set of random numbers which we’ll use for this purpose. Using these random numbers, and depending on the input to the function (which can be 1d, 2d, 3d …) we can control the data returned from the function.

Here is the complete C# 3d Perlin noise code (converted from Ken Perlin’s Advanced Noise Code here):

You can now read my tutorial for 3D Perlin Noise here

public static class c3DPerlinNoise
{
    private static Random rand;  // Tables for Perlin noise permutations
    private static int[] ms_perm = new int[256];
    private static int[] ms_p = new int[512];

    /// Static constructor.  This is needed to create the static instance of
    /// the random class with our seed.  static c3DPerlinNoise()  {  Init();  }
    /// (Re)-Initialises the noise functions.  Can be used to recreate the
    /// noise function with a different seed.

    public static void Init()
    {
        rand = new Random(GalaxisBase.AnswerToUniverse);

        int nbVals = (1 << 8);  // set values as "unused"

        for (int i = 0; i < nbVals; i++)
        {
            ms_perm[i] = -1;
        }

        for (int i = 0; i < nbVals; i++)
        {
            // for each value, find an empty spot, and place it in it
            while (true)
            {
                // generate rand # with max a nbvals
                int p = rand.Next() % nbVals;
                if (ms_perm[p] == -1)
                {
                      ms_perm[p] = i;  break;
                }
            }
        }

        for (int i = 0; i < nbVals; i++)
            ms_p[nbVals + i] = ms_p[i] = ms_perm[i];
    }

    /// x - The first component
    /// y - The second component
    /// z - The third component
    private static double iNoise(double x, double y, double z)
    {
        int  X = (int)Math.Floor(x) & 255,
        Y = (int)Math.Floor(y) & 255,
        Z = (int)Math.Floor(z) & 255;

        x -= Math.Floor(x);
        y -= Math.Floor(y);
        z -= Math.Floor(z);

        // compute fade curves for each x,y,z
        double u = _fade(x);
        double v = _fade(y);
        double w = _fade(z);

        // get the hash coordinates of the 8 cube corners.
        int A = ms_p[X    ] + Y, AA = ms_p[A] + Z, AB = ms_p[A + 1] + Z;
        int B = ms_p[X + 1] + Y, BA = ms_p[B] + Z, BB = ms_p[B + 1] + Z;

        // Add blended results from 8 corners of cube.
        double returnValue = _lerp(w, _lerp(v, _lerp(u, _grad(ms_p[AA], x, y, z),
                                             _grad(ms_p[BA    ], x - 1, y, z)),
                                  _lerp(u, _grad(ms_p[AB    ], x, y - 1, z),
                                              _grad(ms_p[BB    ], x - 1, y - 1, z))),
                      _lerp(v, _lerp(u, _grad(ms_p[AA + 1], x, y, z - 1),
                                             _grad(ms_p[BA + 1], x - 1, y, z - 1)),
                                _lerp(u,  _grad(ms_p[AB + 1], x, y - 1, z - 1),
                                              _grad(ms_p[BB + 1], x - 1, y - 1, z - 1))));

        return returnValue;

    }

    /// Smooth interpolation parameter
    private static double _fade(double t)
    {
        return (t * t * t * (t * (t * 6 - 15) + 10));
    }

    /// Linear interpolation
    private static double _lerp(double t, double a, double b)
    {
        return (a + t * (b - a));
    }

    /// Gradiant
    private static double _grad(int hash, double x, double y, double z)
    {
        int h = hash & 15;
        double u = h < 8 ? x : y;
        double v = h < 4 ? y : h == 12 || h == 14 ? x : z;
        return (((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v));
    }
} // end class

Ok, there’s a fair bit of code there, and quite a few functions. The main function you’ll use is iNoise(), which as you can see, accepts the 3d input (coordinates for instance), and should produce a result in the [-1,1] range.

The other functions (_grad(), _fade() and _lerp()) are all ‘utility’ functions, which are used to blend the results, to ensure we get nice transitions between similar input values.

We could just call this function and get the 3d noise value for a given x,y,z coordinates, but it would be better if we call the inoise function several times (each called an octave), and we can ‘control’ the output a little. For example:
public static double Noise3D(double x, double y, double z, float octaves, float persistence, float gain) { double sum = 0; float amp = 0.5f; float freq = 1.0f; // Add finer and finer ‘hues’ of smoothed noise together for (int o = 0; o < octaves; o++) { sum += SmoothNoise(x * freq , y * freq , z * freq ) * amp; freq *= persistence;
amp *= gain; } //Return the result return sum; }
This will produce noise with x amount of octaves. The persistance changes how the coordinates are sampled in the noise, and the amplitude effects the output of the iNoise function. The gain is used to amplify the output (so we can create really sharp heightmaps etc.)
We can also create ridgeMF and fBm noise as follows:

// Ridged multifractal
// See "Texturing & Modeling, A Procedural Approach", Chapter 12
private static double ridge(float h, float offset)
{
h = abs(h);
h = offset - h;
h = h * h;
return h;
}
// Ridged multifractal
public static double ridgedmf(double x, double y, double z, int octaves, float persistence, float gain, float offset)
{
double sum = 0;     // The return result
double freq = 1.0; // Sample the first octave at normal frequency.
double amp = 0.5; // Reduce the amplitude per octave of noise
double prev = 1.0;
for(int i=0; i<octaves; i++)
{
double n = ridge(inoise(x*freq, y * freq, z * freq), offset);
sum += n*amp*prev;
prev = n;
freq *= persistence;
amp *= gain;
}
// Return the result
return sum;
}

/// Fractal Sum
public static double fBm(double x, double y, double z, int octaves, float persistence, float gain)
{
double freq = 1.0f;  // Sample the first octave at normal frequency.
double amp = 0.5f; // Reduce the amplitude per octave of noise
double sum = 0;      // The return result
for(int i=0; i<octaves; i++)
{
sum += inoise(x.X*freq, y.Y * freq, z.Z * freq)*amp;
freq *= persistence;
amp *= gain;
}
// Return the result.
return sum;
}

n.b. this code came from GPU Gems II Implementing improved Perlin noise on the GPU.
And that’s it! As an idea of something you could try, I am currently trying to improve the star field textures of the skybox. Using this function, it should be possible to define 6 voxel grids in a cube shape, then using the each coordinate as 3d noise input, I should be able to make nice transitions of nebulae which ‘wrap’ around the skybox textures, leaving no visible seems. Well, that’s the dream!

Advertisements
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: