java - Perlin噪声的输出范围

标签 java perlin-noise

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

根据this frequently linked Math FAQ ,输出范围将在 -11 之间,但我不明白该值如何在该范围内。

据我了解,该算法基本上是这样的:每个网格点都有一个长度为1的关联随机梯度 vector 。然后,对于每个点,对于所有四个周围网格点,计算随机梯度和来自该网格点的 vector 的点积。然后,您使用奇特的缓动曲线和线性插值将其降低到一个值。

但是,我的问题是:这些点积有时会超出 [-1, 1] 范围,并且由于您最终在点积之间进行线性插值,这是否意味着最终值有时会超出 [-1, 1] 范围?

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

我在这里遗漏了什么吗?

作为引用,这是我的 Java 代码。 Vec 是一个执行简单 2D vector 算术的简单类,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。这肯定会影响您的输出范围,也是您看到如此大的超出范围值的原因。

<小时/>

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

关于java - Perlin噪声的输出范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31017749/

相关文章:

algorithm - 使用另一种算法修复 Perlin 噪声产生的方向性伪影

algorithm - 如何在球面上生成 Perlin 噪声?

c++ - 制作随机柏林噪声时如何避免模式

c# - 如何让 FastNoise Lite 正常工作?

java - jdbc mysql loginTimeout 不起作用

java - Apache Mahout 没有给出任何建议

java - Spring中Session/HttpSession对象中存储对象列表

java - 在运行时添加监听器? -Java MVC

java - 获取 java 程序运行时的当前堆栈跟踪

perlin-noise - 佩林噪声梯度函数