我正在研究相干噪声的各种实现中的一些(我知道有库,但这主要是为了我自己的启发和好奇心)以及如何使用它,并且我有一个问题与原始柏林噪音的事情。
根据this frequently linked Math FAQ,输出范围介于-1
和1
之间,但我不明白该值是如何在该范围内的。
据我了解,算法基本上是这样的:每个网格点都有一个长度为1
的关联随机梯度向量。然后,对于每个点,对于所有四个周围网格点,您计算随机梯度的点积和从该网格点开始的矢量。然后使用花式缓动曲线和线性插值将其降低到一个值。
但是,这是我的问题:这些点积有时会超出范围[-1, 1]
,并且因为你最终在点积之间进行线性插值,这并不意味着最终有时,价值会超出[-1, 1]
的范围?
比如说,其中一个随机向量是(sqrt(2)/2, sqrt(2)/2)
(长度为1)和(0.8, 0.8)
(单位为正方形),你会得到大约{的结果{1}}。如果在线性插值中使用该值,则生成的值完全有可能大于1.131
。事实上,通过我的直接实施,这种情况经常发生。
我在这里错过了什么吗?
供参考,这是我在Java中的代码。 1
是一个简单的类来进行简单的二维矢量算法,Vec
是缓动曲线,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
的位置。
答案 0 :(得分:9)
您有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)。
我正在使用以下程序测试您的实现(我从您的示例中将其一起攻击,因此请原谅gridCells
和gridSize
的奇怪用法:
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范围内。这是因为内插的点积的距离矢量指向内插轴的相反方向。在最后一次插值期间,输出不会超过-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))。