Perlin噪声的输出范围



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

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

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

但是,我的问题是:这些点积偶尔会在[-1, 1]的范围之外,因为你最终在点积之间进行线性插值,这不意味着最终值有时会在[-1, 1]的范围之外吗?

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

我是不是遗漏了什么?

作为参考,这里是我的Java代码。Vec是一个简单的类,用于进行简单的二维矢量运算,fade()是缓和曲线,lerp()是线性插值,gradient(x, y)将该网格点的梯度作为VecgridSize变量提供以像素为单位的网格大小(类型为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));

其中genjava.util.Random

你有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之间;这可能只是值分布和低采样率的结果。

计算点积时,可能会得到-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)是常见的错误。在这种情况下,最终输出范围仍然是-1+1范围,但-0.707+0.707范围内的值将更频繁。为了避免这个问题,(1,0)(0,1)(-1,0),(0,-1)向量应该替换为(sqrt(2),0)。

相关内容

  • 没有找到相关文章

最新更新