我调查了几个用于相干噪声的各种实现的(我知道有图书馆,但是这主要是为我自己的熏陶和好奇心),以及如何使用它,并且有一个问题我已经与原来的Perlin杂事。
根据这个往往与数学FAQ ,输出范围将介于-1
和1
,但我不明白的价值如何获取要在这个范围内。
据我所知,该算法基本上是这样:每个网格点具有长度的相关的随机梯度矢量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
。
你已经y0 = (int)dx;
,但你的意思是dy
。 这肯定会影响你的输出范围,并且是你看到这样大大超出范围的值的原因。
这就是说,Perlin杂的输出范围是不实际[-1,1]。 虽然我不太清楚自己的数学(我一定是老了)的, 这个相当冗长的讨论工作的是实际的范围是[-sqrt(N)/ 2,开方(N)/ 2],其中n为维(2你的情况)。 所以,你的2D柏林噪声函数的输出范围应为[-0.707,0.707]。 这是某种相关的事实,即d
和插补参数的函数p
。 如果您通过阅读讨论,你会发现你正在寻找的准确的解释(特别是后#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范围。
对于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))。