【发布时间】:2015-03-01 20:45:03
【问题描述】:
为了提供一些上下文,我一直在用 Java 编写一个基本的 Perlin 噪声实现,在实现播种时,我遇到了一个我无法解释的错误。
为了每次为同一个种子生成相同的随机权重向量,无论查询哪组坐标的噪声水平和顺序,我生成了一个新种子(newSeed),基于以下组合原始种子和权重向量的坐标,并将其用作权重向量随机化的种子:
rnd.setSeed(newSeed);
weight = new NVector(2);
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);
weight.normalize()
其中NVector是矢量数学的自制类。
但是,在运行时,程序会产生非常糟糕的噪音:
经过一番挖掘,我发现每个向量的第一个元素非常相似(因此每个 setSeed() 调用之后的第一个 nextDouble() 调用)导致向量网格中每个向量的第一个元素相似。
这可以通过运行来证明:
long seed = Long.valueOf(args[0]);
int loops = Integer.valueOf(args[1]);
double avgFirst = 0.0, avgSecond = 0.0, avgThird = 0.0;
double lastfirst = 0.0, lastSecond = 0.0, lastThird = 0.0;
for(int i = 0; i<loops; i++)
{
ran.setSeed(seed + i);
double first = ran.nextDouble();
double second = ran.nextDouble();
double third = ran.nextDouble();
avgFirst += Math.abs(first - lastfirst);
avgSecond += Math.abs(second - lastSecond);
avgThird += Math.abs(third - lastThird);
lastfirst = first;
lastSecond = second;
lastThird = third;
}
System.out.println("Average first difference.: " + avgFirst/loops);
System.out.println("Average second Difference: " + avgSecond/loops);
System.out.println("Average third Difference.: " + avgSecond/loops);
找到在程序参数指定的一系列种子上调用setSeed() 方法后生成的第一个、第二个和第三个随机数之间的平均差异;对我来说返回了这些结果:
C:\java Test 462454356345 10000
Average first difference.: 7.44638117976783E-4
Average second Difference: 0.34131692827329957
Average third Difference.: 0.34131692827329957
C:\java Test 46245445 10000
Average first difference.: 0.0017196011123287126
Average second Difference: 0.3416750057190849
Average third Difference.: 0.3416750057190849
C:\java Test 1 10000
Average first difference.: 0.0021601598225344998
Average second Difference: 0.3409914232342002
Average third Difference.: 0.3409914232342002
在这里您可以看到第一个平均差异明显小于其他平均差异,并且似乎随着种子的增加而减小。
因此,通过在设置权重向量之前向 nextDouble() 添加一个简单的虚拟调用,我能够修复我的 perlin 噪声实现:
rnd.setSeed(newSeed);
rnd.nextDouble();
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);
导致:
我想知道为什么在第一次调用 nextDouble()(我没有检查其他类型的随机性)时会出现这种糟糕的变化和/或提醒人们注意这个问题。
当然,这可能只是代表我的一个实施错误,如果有人向我指出,我将非常感激。
【问题讨论】:
-
@geert3 是的,我很想这样做,但不幸的是,由于可以按任何顺序调用不同的坐标值,我需要一种方法来为每个排列可靠地返回相同的数字序列调用顺序,因此必须每次设置种子以选择正确的伪随机数序列。
-
我个人会使用 SipHash,以种子为键,坐标为输入。
-
@CodesInChaos 是的,我现在继续使用散列,这提高了随机性,并且由于散列表,大大提高了性能。感谢班塔尔。
-
作为一般说明:种子应该设置一次和一次。
-
请注意,.NET
Random有 similar issues。