当前位置: 首页 > 知识库问答 >
问题:

柏林噪声输出范围

齐典
2023-03-14

我正在研究一些相干噪声的各种实现(我知道有库,但这主要是为了我自己的启发和好奇心)以及如何使用它,我对最初的Perlin噪声有一个问题。

根据这个经常链接的数学常见问题,输出范围将介于-1和1之间,但我不明白该值是如何在该范围内的。

据我所知,算法基本上是这样的:每个网格点都有一个相关的长度为1的随机梯度向量。然后,对于每个点,对于所有四个周围的网格点,计算随机梯度和从该网格点出发的向量的点积。然后使用一条奇特的缓和曲线和线性插值将其降到一个值。

但是,我的问题是:这些点积偶尔会超出范围,既然你最终在点积之间做了线性插值,那不意味着最终值有时会超出范围吗?

例如,假设其中一个随机向量是(sqrt(2)/2, sqrt(2)/2)(长度为1)和(0.8,0.8)(在单位正方形中),您会得到大致1.131的结果。如果在线性插值中使用该值,则生成的值完全有可能大于1。事实上,在我的直截了当的实现中,这种情况经常发生。

我错过什么了吗?

作为参考,这里是我的Java代码Vec是一个简单的类,用于执行简单的2d向量算法,fade()是缓和曲线,lerp()是线性插值,gradient(x,y)将该网格点的梯度作为Vec提供给您。gridSize变量以像素为单位提供网格大小(它的类型为double):

public double getPoint(int x, int y) {
    Vec p = new Vec(x / gridSize, y / gridSize);
    Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));


    int x0 = (int)d.x,
        y0 = (int)d.x;


    double d00 = gradient(x0    , y0    ).dot(p.sub(x0    , y0    )),
           d01 = gradient(x0    , y0 + 1).dot(p.sub(x0    , y0 + 1)),
           d10 = gradient(x0 + 1, y0    ).dot(p.sub(x0 + 1, y0    )),
           d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));

    double fadeX = fade(p.x - d.x),
           fadeY = fade(p.y - d.y);

    double i1 = lerp(fadeX, d00, d10),
           i2 = lerp(fadeX, d01, d11);

    return lerp(fadeY, i1, i2);
}

编辑:以下是生成随机渐变的代码:

double theta = gen.nextDouble() * 2 * Math.PI; 
gradients[i] = new Vec(Math.cos(theta), Math.sin(theta));

其中,gen是java。util。随机

共有2个答案

郎经纶
2023-03-14

计算点积时,可能得到的值超出-1 1范围,但在插值步骤中,最终值落在-1 1范围内。这是因为被插值的点积的距离向量指向插值轴的相反方向。在最后一次插值期间,输出将不超过-1 1范围。

Perlin噪声的最终输出范围由梯度向量的长度定义。如果我们谈论2D噪声,并且我们的目标是使输出范围为1 1,那么梯度向量的长度应该是sqrt(2)(~ 14142)。混合这些向量(1,1)(-1,1)(1,-1)(-1,-1)和(1,0)(0,1)(-1,0)(0,-1)是常见的错误。在这种情况下,最终输出范围仍将为-1 1范围,但是-0.707 0.707范围内的值将更频繁。为避免此问题,应将(1,0)(0,1)(-1,0)(0,-1)向量替换为(sqrt(2),0)(0,sqrt(2))(-sqrt(2),0)(0,-sqrt(2))。

子车青青
2023-03-14

您有y0=(int)d. x;,但您的意思是d. y。这肯定会影响您的输出范围,这也是您看到如此大量超出范围的值的原因。

也就是说,柏林噪声的输出范围实际上不是[-1,1]。虽然我自己对数学不是很确定(我一定是老了),但这个相当长的讨论得出,实际范围是[-sqrt(n)/2,sqrt(n)/2],其中n是维度(在你的例子中是2)。因此,2D Perlin噪波函数的输出范围应为[-0.707,0.707]。这在某种程度上与以下事实有关,即d和插值参数都是p的函数。如果你通读了这篇讨论,你可能会找到你想要的确切解释(尤其是第7篇)。

我正在使用以下程序测试您的实现(我从您的示例中破解了这些程序,因此请原谅gridCellsgridSize的奇怪使用):

import java.util.Random;


public class Perlin {

    static final int gridSize = 200;
    static final int gridCells = 20;
    static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1];

    static void initializeGradient () {
        Random rand = new Random();
        for (int r = 0; r < gridCells + 1; ++ r) {
            for (int c = 0; c < gridCells + 1; ++ c) {
                double theta = rand.nextFloat() * Math.PI;
                gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));                
            }
        }
    }

    static class Vec {
        double x;
        double y;
        Vec (double x, double y) { this.x = x; this.y = y; }
        double dot (Vec v) { return x * v.x + y * v.y; }
        Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); }
    }

    static double fade (double v) {
        // easing doesn't matter for range sample test.
        // v = 3 * v * v - 2 * v * v * v;
        return v;
    }

    static double lerp (double p, double a, double b) {
        return (b - a) * p + a;
    }

    static Vec gradient (int c, int r) {
        return gradients[c][r];
    }

    // your function, with y0 fixed. note my gridSize is not a double like yours.     
    public static double getPoint(int x, int y) {

        Vec p = new Vec(x / (double)gridSize, y / (double)gridSize);
        Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));

        int x0 = (int)d.x,
            y0 = (int)d.y;

        double d00 = gradient(x0    , y0    ).dot(p.sub(x0    , y0    )),
               d01 = gradient(x0    , y0 + 1).dot(p.sub(x0    , y0 + 1)),
               d10 = gradient(x0 + 1, y0    ).dot(p.sub(x0 + 1, y0    )),
               d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));

        double fadeX = fade(p.x - d.x),
               fadeY = fade(p.y - d.y);

        double i1 = lerp(fadeX, d00, d10),
               i2 = lerp(fadeX, d01, d11);

        return lerp(fadeY, i1, i2);

    }

    public static void main (String[] args) {

        // loop forever, regenerating gradients and resampling for range. 
        while (true) {

            initializeGradient();

            double minz = 0, maxz = 0;

            for (int x = 0; x < gridSize * gridCells; ++ x) {
                for (int y = 0; y < gridSize * gridCells; ++ y) {
                    double z = getPoint(x, y);
                    if (z < minz)
                        minz = z;
                    else if (z > maxz)
                        maxz = z;
                }
            }

            System.out.println(minz + " " + maxz);

        }

    }

}

我看到的值在理论范围内[-0.707,0.707],尽管我通常看到的值在-0.6和0.6之间;这可能只是值分布和低采样率的结果。

 类似资料:
  • 我使用柏林噪声生成2D高度图。起初,我手动尝试了一些参数,并为我的工作找到了振幅、持久性...的良好组合。 现在我正在开发该程序,我为用户添加了更改贴图参数并为自己制作新贴图的功能,但现在我发现某些参数(主要是倍频程和频率)的值不在我以前看到的范围内。我认为,如果设定的振幅=20,我从中得到的值(高度)将在[0,20]或[-10,10]或[-20,20]范围内,但现在我看到振幅不是控制输出范围的唯

  • 我有一个柏林噪声函数,我想用它来为我的游戏挑选生物群落地图。问题是,生物群落是由两个因素决定的——平均降雨量和平均温度。所以,我想,我只需要做两个柏林噪声函数,然后重叠它们。 现在的问题是,生物群落并不包括所有可能的降水温度组合。例如,没有高降雨量和低温的生物群落,如图所示。 我怎么能仍然使用柏林噪声,但永远无法到达生物群落未覆盖的区域?

  • 我最近一直在尝试用C实现一个Perlin噪声发生器(基于Ken Perlin的网站,使用SDL库作为屏幕输出),但输出显示插值块之间的边不是连续的或平滑的-插值块确实表现为块。 我很确定我的代码中缺少了一些东西,或者做错了,但我似乎找不到它。 作为参考,我的当前代码如下: 用法:跑步时,按住上下键可放大或缩小噪声网格。

  • 我想让3D Perlin噪波算法适应较低的维度,但我在梯度函数方面遇到了问题,因为我不完全理解推理。 原始的Perlin梯度函数采用四个参数:一个和一个三维坐标。函数的结果基于的值返回,如下所示。 : : : : : : : : : : : : : : : : 从0到11的返回值构成了一种模式,因为每个组合只表示一次。然而,最后四个是重复的。为什么选择它们来拟合最后四个返回值?有两个维度(x,y)

  • GaussianNoise层 因为这是一个起正则化作用的层,该层只在训练时才有效。 GaussianDropout层 因为这是一个起正则化作用的层,该层只在训练时才有效。 AlphaDropout Alpha Dropout是一种保持输入均值和方差不变的Dropout,该层的作用是即使在dropout时也保持数据的自规范性。 通过随机对负的饱和值进行激活,Alphe Drpout与selu激活函数

  • GaussianNoise层 keras.layers.noise.GaussianNoise(sigma) 为层的输入施加0均值,标准差为sigma的加性高斯噪声。该层在克服过拟合时比较有用,你可以将它看作是随机的数据提升。高斯噪声是需要对输入数据进行破坏时的自然选择。 一个使用噪声层的典型案例是构建去噪自动编码器,即Denoising AutoEncoder(DAE)。该编码器试图从加噪的输