用于地形生成的Perlin噪声

时间:2015-02-27 01:25:04

标签: math perlin-noise

我试图实现2D Perlin噪音来创建类似Minecraft的地形(Minecraft实际上并没有使用2D Perlin噪音)而没有悬挑或洞穴和东西。

我这样做的方法是创建一个[50] [20] [50]立方体数组,其中[20]将是数组的最大高度,其值将由柏林噪音。然后我将使用cube数组填充该数组。

我一直在阅读this article并且我不明白,如何计算4梯度向量并在我的代码中使用它?每个相邻的2D数组如[2] [3]和[2] [4]是否都有不同的4个梯度向量?

另外,我已经读过一般的Perlin噪音函数也会采用一个将用作种子的数值,在这种情况下我该把它放在哪里?

3 个答案:

答案 0 :(得分:4)

我将使用工作代码解释Perlin噪音,而不依赖于其他解释。首先,您需要一种在2D点生成伪随机浮点的方法。每个点应该相对于其他点看起来是随机的,但诀窍是相同的坐标应该始终产生相同的浮点数。我们可以使用任何哈希函数来做到这一点 - 不仅仅是Ken Perlin在他的代码中使用的哈希函数。这是一个:

static float noise2(int x, int y) {
    int n = x + y * 57;
    n = (n << 13) ^ n;
    return (float) (1.0-((n*(n*n*15731+789221)+1376312589)&0x7fffffff)/1073741824.0);
}

我用这个来生成一个&#34;风景&#34; landscape[i][j] = noise2(i,j);(然后我转换为图像)并且它总是产生相同的东西:

noise noise noise noise ...

但是看起来随机 - 就像山丘和山谷太密集了。我们需要一种方式来伸展&#34;每个随机点,比如5分。对于那些&#34; key&#34;之间的价值观点,你想要一个平滑的渐变:

static float stretchedNoise2(float x_float, float y_float, float stretch) {
    // stretch
    x_float /= stretch;
    y_float /= stretch;
    // the whole part of the coordinates
    int x = (int) Math.floor(x_float);
    int y = (int) Math.floor(y_float);
    // the decimal part - how far between the two points yours is
    float fractional_X = x_float - x;
    float fractional_Y = y_float - y;
    // we need to grab the 4x4 nearest points to do cubic interpolation
    double[] p = new double[4];
    for (int j = 0; j < 4; j++) {
        double[] p2 = new double[4];
        for (int i = 0; i < 4; i++) {
            p2[i] = noise2(x + i - 1, y + j - 1);
        }
        // interpolate each row
        p[j] = cubicInterp(p2, fractional_X);
    }
    // and interpolate the results each row's interpolation
    return (float) cubicInterp(p, fractional_Y);
}
public static double cubicInterp(double[] p, double x) {
    return cubicInterp(p[0],p[1],p[2],p[3], x);
}
public static double cubicInterp(double v0, double v1, double v2, double v3, double x) {
    double P = (v3 - v2) - (v0 - v1);
    double Q = (v0 - v1) - P;
    double R = v2 - v0;
    double S = v1;
    return P * x * x * x + Q * x * x + R * x + S;
}

如果您不了解详情,那没关系 - 我不知道Math.cos()是如何实施的,但我仍然知道它的作用。而这个功能给我们提供了拉伸,平滑的噪音。

noise -> noise2

stretchedNoise2功能会生成&#34;风景&#34;在一定规模(大或小) - 一个随机点的景观,它们之间有平滑的斜坡。现在我们可以在彼此之上生成一系列景观:

public static double perlin2(float xx, float yy) {
    double noise = 0;
    noise += stretchedNoise2(xx, yy,  5) * 1; // sample 1
    noise += stretchedNoise2(xx, yy, 13) * 2; // twice as influential

    // you can keep repeating different variants of the above lines
    // some interesting variants are included below.

    return noise / (1+2); // make sure you sum the multipliers above
}

为了更准确地说,我们得到每个样本的加权平均值。

noise2 + 2 * noise3)/ 3 = enter image description here

当你将一堆平滑的噪音叠加在一起时,通常会有大约5个增加&#34;拉伸&#34;的样本,你会得到Perlin噪音。 (如果你理解了最后一句,你就会理解Perlin的噪音。)

还有其他一些实现更快,因为它们以不同的方式做同样的事情,但因为它不再是1983年而且因为你开始编写景观生成器,所以你不需要知道所有他们用来理解柏林噪音或用它做有趣事情的特殊技巧和术语。例如:

1)noise 2)noise 3)noise

    // 1
    float smearX = interpolatedNoise2(xx, yy, 99) * 99;
    float smearY = interpolatedNoise2(xx, yy, 99) * 99;
    ret += interpolatedNoise2(xx + smearX, yy + smearY,  13)*1;

    // 2
    float smearX2 = interpolatedNoise2(xx, yy, 9) * 19;
    float smearY2 = interpolatedNoise2(xx, yy, 9) * 19;
    ret += interpolatedNoise2(xx + smearX2, yy + smearY2,  13)*1;

    // 3
    ret += Math.cos( interpolatedNoise2(xx , yy , 5)*4) *1;

答案 1 :(得分:0)

关于perlin噪音

Perlin噪声被开发用于生成随机连续表面(实际上是程序纹理)。其主要特点是噪声在空间上始终是连续的。

来自文章:

  

Perlin噪声是在空间上产生相干噪声的功能。相干噪声意味着对于空间中的任何两个点,当您从一个点移动到另一个点时,噪声函数的值会平滑地变化 - 也就是说,没有不连续性。

简单地说,perlin噪音看起来像这样:

 _         _    __
   \    __/ \__/  \__
    \__/

但这肯定不是柏林的噪音,因为存在差距:

 _         _ 
  \_    __/  
    ___/    __/

计算噪音(或破碎梯度!)

正如@markspace所说,perlin噪音在数学上很难。让我们通过产生一维噪声来简化。

想象一下以下的1D空间:

________________

首先,我们定义一个网格(或一维空间中的点):

1    2    3    4
________________

然后,我们随机为每个网格点选择一个噪声值(此值相当于2D噪声中的梯度):

1    2    3    4
________________
-1   0    0.5  1  // random noise value

现在,计算网格点的噪声值很简单,只需选择值:

noise(3) => 0.5

但是,任意点p的噪声值需要使用它们的值和影响基于最近的网格点p1p2进行计算:

// in 1D the influence is just the distance between the points
noise(p)   => noise(p1) * influence(p1) + noise(p2) * influence(p2)
noise(2.5) => noise(2)  * influence(2, 2.5) + noise(3) * influence(3, 2.5)
           => 0 * 0.5 + 0.5 * 0.5 => 0.25

结束!现在我们可以计算一维噪声,只需为2D添加一维。 : - )

希望它能帮助您理解!现在阅读@mk.'s answer了解工作代码并发出快乐的声音!

修改

评论中的跟进问题:

  

我在维基百科文章中读到,2d perlin中的梯度向量应该是1(单位圆)和随机方向的长度。因为vector有X和Y,我该怎么做呢?

这可以很容易地从original perlin noise code中解除和改编。找到一个伪代码。

gradient.x = random()*2 - 1;
gradient.y = random()*2 - 1;
normalize_2d( gradient );

normalize_2d的位置:

// normalizes a 2d vector
function normalize_2d(v)
   size = square_root( v.x * v.x + v.y * v.y );
   v.x = v.x / size;
   v.y = v.y / size;

答案 2 :(得分:-1)

在坐标x,y

处计算Perlin噪声
function perlin(float x, float y) {

    // Determine grid cell coordinates
    int x0 = (x > 0.0 ? (int)x : (int)x - 1);
    int x1 = x0 + 1;
    int y0 = (y > 0.0 ? (int)y : (int)y - 1);
    int y1 = y0 + 1;

    // Determine interpolation weights
    // Could also use higher order polynomial/s-curve here
    float sx = x - (double)x0;
    float sy = y - (double)y0;

    // Interpolate between grid point gradients
    float n0, n1, ix0, ix1, value;
    n0 = dotGridGradient(x0, y0, x, y);
    n1 = dotGridGradient(x1, y0, x, y);
    ix0 = lerp(n0, n1, sx);
    n0 = dotGridGradient(x0, y1, x, y);
    n1 = dotGridGradient(x1, y1, x, y);
    ix1 = lerp(n0, n1, sx);
    value = lerp(ix0, ix1, sy);

    return value;
}