【问题标题】:Perlin noise for terrain generation用于地形生成的 Perlin 噪声
【发布时间】:2015-02-27 01:25:03
【问题描述】:

我正在尝试实现 2D Perlin 噪声来创建类似 Minecraft 的地形(Minecraft 实际上并没有使用 2D Perlin 噪声),而没有悬垂或洞穴之类的东西。

我这样做的方式是创建一个 [50][20][50] 立方体数组,其中 [20] 将是数组的最大高度,其值将由 Perlin 噪声确定.然后我将用立方体数组填充该数组。

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

另外,我读到一般 Perlin 噪声函数也采用将用作种子的数值,在这种情况下我应该把它放在哪里?

【问题讨论】:

标签: math perlin-noise


【解决方案1】:

我将使用工作代码来解释 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);
}

我用它来生成“风景”landscape[i][j] = noise2(i,j);(然后我将其转换为图像),它总是产生相同的东西:

...

但这看起来随机 - 就像山丘和山谷过于密集。我们需要一种将每个随机点“拉伸”到 5 点以上的方法。对于这些“关键”点之间的值,您需要平滑渐变:

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() 是如何实现的,但我仍然知道它的作用。这个函数给我们带来了拉伸、平滑的噪声。

-&gt;

stretchedNoise2 函数生成一定比例(大或小)的“景观”——随机点的景观,它们之间有平滑的斜坡。现在我们可以生成一系列相互叠加的风景:

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
}

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

( + 2 * ) / 3 =

当您将一堆平滑噪声堆叠在一起时,通常是大约 5 个增加“拉伸”的样本,您会得到 Perlin 噪声。 (如果你理解了最后一句话,你就理解了 Perlin 噪音。)

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

1) 2) 3)

    // 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;

【讨论】:

  • 对不起,我不太了解你:/你描述的第一步,实际上是维基百科文章中的哪一步?
  • 我可以解释 Perlin 噪音,但我无法解释其他人的解释。其中许多是不必要的混乱(尽管some are good)。如果您无法理解上述内容,请告诉我。我做了一些编辑并添加了一些可能有帮助的图片。
  • @mk。我知道我迟到了将近 7 年,但我只是想让你知道,这个答案对我长期苦苦挣扎的 Perlin 噪声实施问题有很大帮助。非常感谢!
【解决方案2】:

关于柏林噪声

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

来自文章:

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

简单地说,柏林噪声看起来像这样:

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

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

 _         _ 
  \_    __/  
    ___/    __/

计算噪声(或压碎梯度!)

正如@markspace 所说,柏林噪声在数学上很难。让我们通过生成 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

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

希望能帮助你理解!现在阅读@mk.'s answer 以获取工作代码并享受快乐的声音!

编辑:

在 cmets 中跟进问题:

我在维基百科文章中读到,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;

【讨论】:

  • 我在维基百科文章中读到 2d perlin 中的梯度向量应该是长度为 1(单位圆)和随机方向。由于向量有 X 和 Y,我该怎么做呢?
  • 注意:我删除了一些 cmets,因为它们与 2D perling 噪声(但与 1D perling 噪声)无关,并且对您的后续问题没有建设性
  • 我更新了答案,包括如何生成具有随机方向的归一化向量(大小 1)。
  • @Filipee Borges 感谢您的回答。有些事情我仍然不明白,在大多数 perlin 噪声库中(我在处理和统一中看到),perlin 噪声函数采用种子编号,因此结果不同,但结果始终相同相同的种子数(在我的问题的文章中,对于相同的 X 和 Y,4 梯度也始终相同),在这些数学步骤中,该种子数在哪里使用?
  • 种子数是伪随机生成器的种子。 @see:docs.oracle.com/javase/7/docs/api/java/util/…cplusplus.com/reference/cstdlib/srand
【解决方案3】:

计算坐标 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;
}

【讨论】:

    猜你喜欢
    • 2011-06-12
    • 2020-08-30
    • 2011-08-30
    • 2014-05-30
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 2011-09-20
    • 2012-03-08
    相关资源
    最近更新 更多