My Perlin Noise function having too many dark spots, and little strips of white

我是研究僧i 提交于 2021-01-28 12:26:33

问题


I created a Perlin noise function by loosely following a java tutorial in C#. I then used Unity to visualize it (I don't think the issue is with unity, as their inbuilt func works perfectly)

The problem is that there are too many large dark spots. Here is a photo

This is how I would like it to look

As you can see, it looks as if the points are not blending well.

Here is my code for the noise:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PerlinNoise
{
    public PerlinNoise()
    {
    }

    public double GetPixel(double x, double y)
    {
        int xint = (int)Math.Floor(x) & 255;
        int yint = (int)Math.Floor(y) & 255;    //gets what cube the input coords are


        int g1 = p[p[xint] + yint];
        int g2 = p[p[xint + 1] + yint];
        int g3 = p[p[xint] + yint + 1];
        int g4 = p[p[xint + 1] + yint + 1];     //gets the gradient vector's vaule on the permutation table

        double xdec = x - Math.Floor(x);
        double ydec = y - Math.Floor(y);    //gets the pixel inside the cube

        double d1 = grad(g1, xdec, ydec);
        double d2 = grad(g2, xdec - 1, ydec);
        double d3 = grad(g3, xdec, ydec - 1);
        double d4 = grad(g4, xdec - 1, ydec - 1);

        double u = fade(xdec);
        double v = fade(ydec);

        double x1Inter = lerp(u, d1, d2);
        double x2Inter = lerp(u, d3, d4);
        double yInter = lerp(v, x1Inter, x2Inter);

        return yInter;
    }

    private static double lerp(double amount, double left, double right)
    {
        return ((1 - amount) * left + amount * right);
    }

    private static double fade(double t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    private static double grad(int hash, double x, double y)
    {
        switch (hash & 3)
        {
            case 0: return x + y;
            case 1: return -x + y;
            case 2: return x - y;
            case 3: return -x - y;
            default: return 0;
       }
   }

    private static readonly int[] p = { 151,160,137,91,90,15,
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,    
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,151,160,137,91,90,15,
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
    };

}

Here is the unity texture code:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices.ComTypes;

// Create a texture and fill it with Perlin noise.
// Try varying the xOrg, yOrg and scale values in the inspector
// while in Play mode to see the effect they have on the noise.

public class TextureApply : MonoBehaviour
{
    // Width and height of the texture in pixels.
    public int pixWidth;
    public int pixHeight;

    // The origin of the sampled area in the plane.
    public float xOrg;
    public float yOrg;

    // The number of cycles of the basic noise pattern that are repeated
    // over the width and height of the texture.
    public float scale = 1.0F;

    private Texture2D noiseTex;
    private Color[] pix;
    private Renderer rend;

    void Start()
    {
        rend = GetComponent<Renderer>();

        // Set up the texture and a Color array to hold pixels during processing.
        noiseTex = new Texture2D(pixWidth, pixHeight);
        pix = new Color[noiseTex.width * noiseTex.height];
        rend.material.mainTexture = noiseTex;

        CalcNoise();
    }

    void CalcNoise()
    {
        // For each pixel in the texture...
        float y = 0.0F;

         while (y < noiseTex.height)
        {
            float x = 0.0F;
            while (x < noiseTex.width)
             {
                double xCoord = xOrg + x / noiseTex.width * scale;
                double yCoord = yOrg + y / noiseTex.height * scale;

                PerlinNoise perlin = new PerlinNoise();
                double sample = perlin.GetPixel(xCoord, yCoord);
            
                pix[(int)y * noiseTex.width + (int)x] = new Color((float)sample, (float)sample, (float)sample);
                x++;
            }
            y++;
        }

        // Copy the pixel data to the texture and load it into the GPU.
        noiseTex.SetPixels(pix);
        noiseTex.Apply();
    }

    void OnValidate()
    {
        CalcNoise();
    }
}

Above is just modified code from docs.unity3d.com

I think the issue might be with the permutation table, instead of doing some convoluted way of doubling the number of values, I just copied and pasted it.

EDIT: [This is a picture of what I'm getting when I change this to Simplex Noise][1] [1]: https://i.stack.imgur.com/jfxNr.png


回答1:


It's the gradient set being used. Directions (1, 1) point directly at diagonal neighbors. If those diagonal neighbors have (-1, -1), then they constructively interfere. Same for other corner pairs, and for when they point negative instead of positive at each other. EDIT: You can try +x + ROOT2*y, +ROOT2*x + y, and all sign permutations thereof, to produce 8 gradients.

Also, even if you improve the gradients, you may have heard that Perlin is an older method for noise, which produces a lot of 45 and 90 degree bias. There aren't a lot of cases where I recommend it. If you want to implement noise as a programming exercise, that's great. But if you want to use noise in a project, I would either code or import a good 2D simplex implementation.

EDIT: See comments. I looked at the wrong image. Solution you need, is only sample = sample * 0.5 + 0.5 to rescale the output so that the negative values don't all get cut off in the image.

Also if you want to convert your noise into a rudimentary simplex, you can do this:

  1. Define constants double F2 = 0.366025403784439 and double G2 = -0.211324865405187. These will be used below, to work with the triangular grid.
  2. At the beginning of GetPixel(double x, double y) add double s = (x + y) * F2; x += s; y += s;. This will convert the coordinates which produce integers on a square grid, to coordinates which produce integers on a diagonally-compressed square grid which represents equilateral triangles.
  3. After you define xdec and ydec, add double t = (xdec + ydec) * G2; xdec += t; ydec += t;. This will unskew these coordinates relative to the base of the compressed square, so that they can now be used for distance and gradients again.
  4. Define doubles xdec2 = xdec - 1 - G2; ydec2 = ydec - G2;. Define xdec3 = xdec - G2; ydec3 = ydec - 1 - G2;. Define xdec4 = xdec - 1 - 2*G2; ydec4 = ydec - 1 - 2*G2;. You need this, because simply subtracting 1 doesn't work anymore to get the relative coordinates to the other four corners, because the corners aren't aligned with a square anymore. Now, any time you subtract 1 from either of the coordinates, you also need to subtract G2 from both. Thus, for #2 and #3 you subtract G2 once from each, and for #4 you subtract it twice (2*G2). You can re-derive this yourself by seeing what happens if you had already subtracted 1 from one or both of the coordinates, before calculating the previous step where you define double t.
  5. Change d2, d3, d4 to use these values instead, like d2 = grad(g2, xdec2, ydec2);
  6. Remove everything starting with double u and ending with return yInter;. Add double value = 0;. You will add stuff to this value.
  7. Define double a1 = 0.5 - xdec*xdec - ydec*ydec; double a2 = 0.5 - xdec2*xdec2 - ydec2*ydec2; and the same pattern for a3 and a4. These functions represent circles around each triangle vertex, which stop at the triangle edges.
  8. Add if (a1 > 0) value += (a1 * a1) * (a1 * a1) * d1; and the same for a2, a3, a4. These add a value to the noise inside each of those circles, based on the gradient and distance.
  9. If you use the improved gradients I suggested for Perlin, add return value * 38.283687591552734375;. If you use the original ones, I think the right value to multiply by is 75.3929901123046875 but I'm not sure. It will be something close to that, though. Really, you probably want more gradients for simplex, but the 8 should make it look better than Perlin at least.

The noise will not look very good without the improved gradients. You will still see a lot of 45 degree bias. If you want to really improve the noise, here is my suggestion, with 24 gradients. You can find these gradients here. Use 99.83685446303647 as the value you multiply by at the end.

    private static double grad(int hash, double x, double y)
    {
        switch (hash % 24)
        {
            case 0:  return 0.130526192220052 * x +   0.99144486137381 * y;
            case 1:  return 0.38268343236509 * x +    0.923879532511287 * y;
            case 2:  return 0.608761429008721 * x +   0.793353340291235 * y;
            case 3:  return 0.793353340291235 * x +   0.608761429008721 * y;
            case 4:  return 0.923879532511287 * x +   0.38268343236509 * y;
            case 5:  return 0.99144486137381 * x +    0.130526192220051 * y;
            case 6:  return 0.99144486137381 * x +   -0.130526192220051 * y;
            case 7:  return 0.923879532511287 * x +  -0.38268343236509 * y;
            case 8:  return 0.793353340291235 * x +  -0.60876142900872 * y;
            case 9:  return 0.608761429008721 * x +  -0.793353340291235 * y;
            case 10: return 0.38268343236509 * x +   -0.923879532511287 * y;
            case 11: return 0.130526192220052 * x +  -0.99144486137381 * y;
            case 12: return -0.130526192220052 * x + -0.99144486137381 * y;
            case 13: return -0.38268343236509 * x +  -0.923879532511287 * y;
            case 14: return -0.608761429008721 * x + -0.793353340291235 * y;
            case 15: return -0.793353340291235 * x + -0.608761429008721 * y;
            case 16: return -0.923879532511287 * x + -0.38268343236509 * y;
            case 17: return -0.99144486137381 * x +  -0.130526192220052 * y;
            case 18: return -0.99144486137381 * x +   0.130526192220051 * y;
            case 19: return -0.923879532511287 * x +  0.38268343236509 * y;
            case 20: return -0.793353340291235 * x +  0.608761429008721 * y;
            case 21: return -0.608761429008721 * x +  0.793353340291235 * y;
            case 22: return -0.38268343236509 * x +   0.923879532511287 * y;
            case 23: return -0.130526192220052 * x +  0.99144486137381 * y;
            default: return 0;
       }
   }

Optional optimization: You only need one of a2/g2/xdec2/ydec2, a3/g3/xdec3/ydec3 at a time. If ydec > xdec then you can do a3/g3/xdec3/ydec3, otherwise a2/g2/xdec2/ydec2. Almost every tutorial and implementation out there will have this, but you don't need it to make the noise work.

Optional optimization #2: In step 8, you can pull in the definition of d1, and inside that you can pull in the definition of g1. That way, the definitions will only be computed when they're needed inside that conditional (if the compiler doesn't already catch that). if (a1 > 0) value += (a1 * a1) * (a1 * a1) * grad(p[p[xint] + yint], xdec, ydec); Then you can remove them from where they're originally computed.

Note: Some tutorials or implementations will use a positive G2, and flip the additions/subtractions of it. I find that it makes most sense to keep it negative, because then the unskew transform works exactly like the skew transform. But it's worthwhile to keep in mind.



来源:https://stackoverflow.com/questions/63321155/my-perlin-noise-function-having-too-many-dark-spots-and-little-strips-of-white

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!