【问题标题】:Output range of Perlin noisePerlin 噪声的输出范围
【发布时间】:2013-08-15 21:14:35
【问题描述】:

我正在研究一些用于相干噪声的各种实现(我知道有一些库,但这主要是为了我自己的启发和好奇心)以及如何使用它,我对原始版本有一个问题柏林噪音的事情。

根据this frequently linked Math 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 变量以像素为单位为您提供网格的大小(它的类型为 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

【问题讨论】:

    标签: java perlin-noise


    【解决方案1】:

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


    也就是说,Perlin 噪声的输出范围不是实际上是[-1, 1]。虽然我自己对数学不太确定(我一定是老了),this rather lengthy discussion 计算出实际范围是 [-sqrt(n)/2, sqrt(n)/2],其中 n 是维度(在您的情况下为 2)。所以你的 2D Perlin 噪声函数的输出范围应该是 [-0.707, 0.707]。这在某种程度上与d 和插值参数都是p 的函数有关。如果您通读该讨论,您可能会找到您正在寻找的准确解释(特别是 post #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 之间;这可能只是值分布和低采样率的结果。

    【讨论】:

      【解决方案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) (0, -1) 向量应替换为 (sqrt(2), 0) (0, sqrt(2)) (-sqrt (2), 0) (0, -sqrt(2))。

      【讨论】:

        猜你喜欢
        • 2013-06-29
        • 1970-01-01
        • 2014-04-18
        • 2014-02-15
        • 2020-06-06
        • 2011-09-20
        • 2019-08-12
        • 2021-06-30
        相关资源
        最近更新 更多