Perlin噪音的输出范围

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

根据这个经常链接的数学常见问题解答 ,输出范围将介于-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(px), Math.floor(py)); int x0 = (int)dx, y0 = (int)dx; 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(px - dx), fadeY = fade(py - dy); 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)dx; ,但你的意思是dy 。 这肯定会影响您的输出范围,这也是您看到这些大部分超出范围值的原因。


也就是说,Perlin噪声的输出范围实际上并不是[-1,1] 。 虽然我自己不太确定数学(我必须变老), 这个相当冗长的讨论certificate实际范围​​是[-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 * vx + y * vy; } 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(px), Math.floor(py)); int x0 = (int)dx, y0 = (int)dy; 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(px - dx), fadeY = fade(py - dy); 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)(~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))。