如何增加 Perlin 噪声分布的标准偏差

How to increase the standard deviation of a Perlin noise distribution

我一直在关注柏林噪声指南:http://flafla2.github.io/2014/08/09/perlinnoise.html

我使用的代码和他描述的完全一样,虽然我的渲染实现不同。但是,当我从该分布中获取噪声时,我倾向于在 0.5 范围附近获取很大一部分值,而在边缘处获取的值很少。您可以从我在下图中转换成快速图形的示例中看到这一点。我按屏幕宽度缩放噪声函数,并让它在每次生成噪声值时将 index = noise 处的数组递增 1。如您所见,分布几乎不是正态分布,因为边缘完全消失了。 (从技术上讲,它是没有标签和条形线的直方图)

使此分布向底部变宽而不向顶部变宽的最佳方法是什么?我希望大部分值位于高斯分布中,但我不能只使用高斯分布随机数,因为我需要选择彼此靠近的值以彼此接近(柏林噪声)。

我想更好的提问方式是:为什么我在范围的上四分之一和下四分之一处没有噪声值?有没有办法确定一个好的比例因子来扩大图表?

这是我用来获取图像的代码:

/**
 * Simple linear interpolation
 * @param a Start
 * @param b End
 * @param weight weighting
 * @return A linear interpolation between points a and b 
 */
public double lerp(double a, double b, double weight) {
    return a + weight*(b-a);
}

/**
 * Calculates a dot product between a distance vector and a pseudorandom
 * "gradient" vector which gets picked using the hash
 * @param hash
 * @param x distance vector x component
 * @param y distance vector y component
 * @param z distance vector z component (z = 0 for 2D map)
 * @return dot product of <x, y, z> and a pseudorandom gradient vector
 */
public double grad(int hash, double x, double y, double z) {
    switch(hash & 0xF)
    {
        case 0x0: return  x + y;
        case 0x1: return -x + y;
        case 0x2: return  x - y;
        case 0x3: return -x - y;
        case 0x4: return  x + z;
        case 0x5: return -x + z;
        case 0x6: return  x - z;
        case 0x7: return -x - z;
        case 0x8: return  y + z;
        case 0x9: return -y + z;
        case 0xA: return  y - z;
        case 0xB: return -y - z;
        case 0xC: return  y + x;
        case 0xD: return -y + z;
        case 0xE: return  y - x;
        case 0xF: return -y - z;
        default: return 0; // never happens
    }
}

/**
 * A fifth order fade function: 6t^5 - 15t^4 + 10t^3
 * @param t The x-value along the function, t is in [0, 1]
 * @return The y-value for the fade function
 */
public double fade(double t) {
    return t * t * t * (t * (t * 6 - 15) + 10);
}

//repeat is set to 0 so this method is just a regular "increment by 1"
public int inc(int num) {
    num++;
    if(repeat > 0) num %= repeat;
    return num;
}

/**
 * Generates a noise value in the range [0,1].
 * Each coordinate is a given distance from a pseudorandomly picked set of
 * gradient vectors. The vectors are determined by an array of 256 indexes,
 * so the noise pattern inevitably repeats at a scale greater than 255,
 * which is bigger than we should need.
 * @param x
 * @param y
 * @param z
 * @return 
 */
public double perlin(double x, double y, double z) {
    if(repeat > 0) {
        x = x%repeat;
        y = y%repeat;
        z = z%repeat;
    }

    int xi = (int)x & 255;
    int yi = (int)y & 255;
    int zi = (int)z & 255;
    double xf = x-(int)x;
    double yf = y-(int)y;
    double zf = z-(int)z;


    double u = fade(xf);
    double v = fade(yf);
    double w = fade(zf);

    int aaa, aba, aab, abb, baa, bba, bab, bbb;
    aaa = p[p[p[    xi ]+    yi ]+    zi ];
    aba = p[p[p[    xi ]+inc(yi)]+    zi ];
    aab = p[p[p[    xi ]+    yi ]+inc(zi)];
    abb = p[p[p[    xi ]+inc(yi)]+inc(zi)];
    baa = p[p[p[inc(xi)]+    yi ]+    zi ];
    bba = p[p[p[inc(xi)]+inc(yi)]+    zi ];
    bab = p[p[p[inc(xi)]+    yi ]+inc(zi)];
    bbb = p[p[p[inc(xi)]+inc(yi)]+inc(zi)];


    double x1, x2, y1, y2;

    /*
    Box has corners:
    ____
    |ab|
    |cd|
    ----
    Interpolate a-b, then c-d then both of those together, then repeat on the z-1 level
    */
    x1 = lerp(grad (aaa, xf  , yf  , zf),           // The gradient function calculates the dot product between a pseudorandom
              grad (baa, xf-1, yf  , zf),             // gradient vector and the vector from the input coordinate to the 8
              u);                                     // surrounding points in its unit cube.
    x2 = lerp(grad (aba, xf  , yf-1, zf),
              grad (bba, xf-1, yf-1, zf),
              u);
    y1 = lerp(x1, x2, v);

    x1 = lerp(grad (aab, xf  , yf  , zf-1),
              grad (bab, xf-1, yf  , zf-1),
              u);
    x2 = lerp(grad (abb, xf  , yf-1, zf-1),
              grad (bbb, xf-1, yf-1, zf-1),
              u);
    y2 = lerp (x1, x2, v);

    return (lerp (y1, y2, w)+1)/2; //Interpolate everything again and move the range from [-1, 1] to [0, 1]
}

/**
 * Layers levels of noise, each with decreasing amplitudes and persistence
 * @param x
 * @param y
 * @param z
 * @param octaves
 * @param persistence how much each layer impacts the layer below it
 * @return
 */
public double octave(double x, double y, double z, int octaves, double persistence) {
    double total = 0, frequency = 1, amplitude = 1, maxValue = 0;
    for(int i = 0; i < octaves; i++) {
        total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
        maxValue += amplitude;
        amplitude *= persistence;
        frequency *= 2;
    }

    return total/maxValue;
}

这里是启动方法:

public void enter() {
    eOffsetX = r.nextInt(10000);
    eOffsetY = r.nextInt(10000); //This will "randomize" the seed of the noise
    p = new int[512];
    for(int x = 0; x < 512; x++) {
        p[x] = permutation[x%256]; //Fill twice
    }
    eNoise = new float[(int)(1280/tile)][(int)(800/tile)];
    gauss = new float[1280];
    Arrays.fill(gauss, 0);

    for(int i = 0; i < eNoise.length; i++) {
        for(int j = 0; j < eNoise[0].length; j++) {
            eNoise[i][j] = 1f * 100 * (float) octave(((double)i*zoom+eOffsetX)/1280, ((double)j*zoom+eOffsetY)/800, 0, 7, 0.60);
            gauss[(int)(((eNoise[i][j]/100)-.5)*1280*2.5+640)] += 1f;
        }
    }}

渲染方法:

public void render(Graphics g) {
        g.setBackground(Color.white);
        g.setColor(Color.black);
        for(int k = 0; k < 1280; k++) {
            g.fillRect(k, 800-(gauss[k]/10), 5, 5);
        }
}

最后,enter() 中提到的排列集:

private static final int[] permutation = { 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
};

这是从0到255的所有数字,顺序随机。

同样,重申一下这个问题,该函数生成了 0.45 到 0.55 之间的大量数字,几乎没有超出该范围的数字。我想重新缩放函数,以便在较低和较高范围内获得更多数字。这可能发生在 perlin() 函数最后一行的某处,但我不确定该怎么做。感谢您的帮助。

我已经通过以下方法解决了这个问题。

首先,在生成噪声值时,我记录了最小值和最大值。

其次,在将噪声值存储到我的数组中时,我对数据进行了标准化: (值 - 最小值)/(最大 - 最小值)。这样,我所有的值都在 0 到 1 之间,但分布得更好。该等式将最小值映射到 (min - min)/(max - min) 或 0,并将最大值映射到 (max - min)/(max - min) 或 1,并缩放介于两者之间的所有值。