Perlin杂的输出范围(Output range of Perlin noise)

2019-10-23 11:52发布

我调查了几个用于相干噪声的各种实现的(我知道有图书馆,但是这主要是为我自己的熏陶和好奇心),以及如何使用它,并且有一个问题我已经与原来的Perlin杂事。

根据这个往往与数学FAQ ,输出范围将介于-11 ,但我不明白的价值如何获取要在这个范围内。

据我所知,该算法基本上是这样:每个网格点具有长度的相关的随机梯度矢量1 。 然后,对于每个点,所有周围的四个网格点,你计算的随机梯度,并从该网格点将会向量的点积。 然后你使用一个奇特的缓动曲线和线性插值来获取到一个值。

但是,这里是我的问题:这些点的产品,有时会是超出范围[-1, 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变量提供了电网的像素(它的类型是双)尺寸:

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.Random

Answer 1:

你已经y0 = (int)dx; ,但你的意思是dy 。 这肯定会影响你的输出范围,并且是你看到这样大大超出范围的值的原因。


这就是说,Perlin杂的输出范围是实际[-1,1]。 虽然我不太清楚自己的数学(我一定是老了)的, 这个相当冗长的讨论工作的是实际的范围是[-sqrt(N)/ 2,开方(N)/ 2],其中n为维(2你的情况)。 所以,你的2D柏林噪声函数的输出范围应为[-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之间的值看到; 这可能仅仅是价值分配的结果和低采样率。



Answer 2:

当你计算点的产品,你可能会得到以外的值-1 +1范围,在插值步然而,最终的值在-1 +1范围。 这是因为,被内插点的内插入轴的相反方向上的点产品的距离向量。 在过去的插值输出不会超过-1 +1范围。

对于Perlin杂最终输出范围由梯度矢量的长度限定。 如果我们谈论的是2D的噪声,我们的目标有输出范围-1 +1,梯度向量的长度应的sqrt(2)(〜1,4142)。 这是常见的错误混合这些向量(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)(1,0,-1)的载体应与(SQRT(2),0)(0,SQRT(2))(-sqrt替换(2),0)(0,-sqrt(2))。



文章来源: Output range of Perlin noise