Attempted Perlin Noise in C# but results are all too close to 0

73 Views Asked by At

So I am trying to use 2D Perlin Noise for a Monogame project and I read through the wikipedia article (https://en.wikipedia.org/wiki/Perlin_noise) covering the approach and tried to code it. After a few failed attempts I copied some of the original C code written by Ken Perlin and translated it to C#. The results (which should gradually range from -1 to 1) all result near 0 through an entire cycle. The problem seems to be with the dot products too closely matching each other and giving near 0 results in the interpolate function, but I don't see any differences with the original implementation. I drew a 480x270 heightmap of the function output with a few different scales for x and y.

This is the attempt with x/48 y/27 This is the attempt with x/48 y/27 while going from x = 0 -> 480 and y = 0 -> 270. When trying to divide by bigger numbers it mostly returns gray. When drawing the heightmap, I multiplied by 0.5f and added 0.5f to get the range from 0 to 1 for drawing black to white.

This is the code that I used:

public static float PerlinNoise(float x, float y)
    {
        //Get corner offsets
        //0 is towards top left, 1 is towards bottom right
        int x0 = (int)MathF.Floor(x);
        int x1 = x0 + 1;
        int y0 = (int)MathF.Floor(y);
        int y1 = y0 + 1;
        
        //Calculate dot products between gradient vector and distance vector
        float[] dotProducts = new float[4];
        dotProducts[0] = DotGridGradient(x0, y0, x, y);
        dotProducts[1] = DotGridGradient(x1, y0, x, y);
        dotProducts[2] = DotGridGradient(x0, y1, x, y);
        dotProducts[3] = DotGridGradient(x1, y1, x, y);
        
        //Interpolate the values
        float sx = x - x0;
        float sy = y - y0;
        
        float interpolate1 = Interpolate(dotProducts[0], dotProducts[1], sx);
        float interpolate2 = Interpolate(dotProducts[2], dotProducts[3], sx);

        return Interpolate(interpolate1, interpolate2, sy);
    }

    private static float DotGridGradient(int ix, int iy, float x, float y)
    {
        // Seed the random number generator based on grid cell indices
        int seed = 31 * ix + iy;
        Random random = new Random(seed);
        
        //---Confirmed working---
        //Chooses a random angle between 0 and 2 pi (based on x and y to allow seeding)
        float randomAngle = (float)random.NextDouble() * 2 * MathF.PI;
        Vector2 gradientVector = new (MathF.Sin(randomAngle), MathF.Cos(randomAngle));
        
        //Compute distance vector
        float dx = x - ix;
        float dy = y - iy;

        return dx * gradientVector.X + dy * gradientVector.Y;
    }
    
    private static float Interpolate(float leftEdge, float rightEdge, float weight)
    {
        return (rightEdge - leftEdge) * weight + leftEdge;
    }
1

There are 1 best solutions below

0
Phlips On

After some trail and error I found the answer to my own problem, I'll post it here if anyone needs the solution at some point: The problem was in the random gradient vectors, this was my old solution:

// Seed the random number generator based on grid cell indices
        int seed = 31 * ix + iy;
        Random random = new Random(seed);

//Chooses a random angle between 0 and 2 pi (based on x and y to allow seeding)
        float randomAngle = (float)random.NextDouble() * 2 * MathF.PI;
        Vector2 gradientVector = new (MathF.Sin(randomAngle), MathF.Cos(randomAngle));

I then tried the solution listed on the wikipedia page and that fixed the issue. The code (translated from C to C#) is:

// No precomputed gradients mean this works for any number of grid coordinates
        const int w = 8 * sizeof(int);
        const int s = w / 2; // rotation width
        long a = ix, b = iy;
        a *= 3284157443; b ^= a << s | a >> w - s;
        b *= 1911520717; a ^= b << s | b >> w - s;
        a *= 2048419325;
        float random = a * (float)(Math.PI / ~(~0u >> 1)); // in [0, 2*Pi]
        Vector2 v;
        v.X = (float)Math.Cos(random);
        v.Y = (float)Math.Sin(random);
        return v;