我试图在单位球体的表面上生成均匀的随机点,用于蒙特卡罗射线追踪程序。当我说均匀时,我的意思是这些点相对于表面积均匀分布。我目前的方法是计算半球上的均匀随机点,指向正z轴并基于x-y平面。
半球上的随机点表示漫射灰色发射器的热辐射发射方向。
当我使用以下计算时,我获得了正确的结果:
注意:dsfmt *将返回0到1之间的随机数。
azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
zenith = asin(sqrt(dsfmt_genrand_close_open(&dsfmtt)));
// Calculate the cartesian point
osRay.c._x = sin(zenith)*cos(azimuthal);
osRay.c._y = sin(zenith)*sin(azimuthal);
osRay.c._z = cos(zenith);
然而,这是非常缓慢的,并且分析表明它占用了大部分的运行时间。因此,我找到了一些替代方法:
Marsalalia 1972拒绝方法
do {
x1 = 2.0*dsfmt_genrand_open_open(&dsfmtt)-1.0;
x2 = 2.0*dsfmt_genrand_open_open(&dsfmtt)-1.0;
S = x1*x1 + x2*x2;
} while(S > 1.0f);
osRay.c._x = 2.0*x1*sqrt(1.0-S);
osRay.c._y = 2.0*x2*sqrt(1.0-S);
osRay.c._z = abs(1.0-2.0*S);
分析笛卡尔坐标计算
azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
u = 2*dsfmt_genrand_close_open(&dsfmtt) -1;
w = sqrt(1-u*u);
osRay.c._x = w*cos(azimuthal);
osRay.c._y = w*sin(azimuthal);
osRay.c._z = abs(u);
虽然最后两种方法比第一种方法运行的时间更快,但是当我使用它们时,我得到的结果表明它们不是在球体表面上产生均匀的随机点,而是给出了有利于赤道的分布。
此外,最后两种方法给出相同的最终结果,但我确定它们是错误的,因为我正在与分析解决方案进行比较。
我发现的每个引用都表明这些方法确实产生了均匀的分布,但是我没有达到正确的结果。
我的实施中是否有错误,或者我在第二和第三种方法中错过了一个基本想法?
答案 0 :(得分:15)
在单位球上生成均匀分布的最简单方法(无论其大小是什么)是绘制独立的正态分布并对结果向量进行归一化。
实际上,例如在维3中,e ^( - x ^ 2/2)e ^( - y ^ 2/2)e ^( - z ^ 2/2)= e ^( - (x ^ 2) + y ^ 2 + z ^ 2)/ 2)所以联合分布是由旋转不变的。
如果您使用快速正态分布生成器(Ziggurat或比例制服)和快速归一化程序(google用于“快速反平方根”),这是很快的。不需要超越函数调用。
此外,Marsaglia在半球上不均匀。由于2D光盘上的对应点< - >,因此在赤道附近会有更多的点。半球上的点不是等距的。最后一个似乎是正确的(但我没有进行计算以确保这一点)。
答案 1 :(得分:3)
如果采用高度为h
的单位球体的水平切片,其表面积仅为2 pi h
。 (这就是阿基米德计算球体表面积的方法。)因此,z坐标均匀分布在[0,1]
中:
azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
osRay.c._z = dsfmt_genrand_close_open(&dsfmtt);
xyproj = sqrt(1 - osRay.c._z*osRay.c._z);
osRay.c._x = xyproj*cos(azimuthal);
osRay.c._y = xyproj*sin(azimuthal);
您也可以通过计算cos(azimuthal)
和sin(azimuthal)
来节省一些时间 - 请参阅this stackoverflow question进行讨论。
编辑添加:好的,我现在看到这只是对您的第三种方法的轻微调整。但它缩短了一步。
答案 2 :(得分:2)
你有没有试过摆脱asin
?
azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt);
sin2_zenith = dsfmt_genrand_close_open(&dsfmtt);
sin_zenith = sqrt(sin2_zenith);
// Calculate the cartesian point
osRay.c._x = sin_zenith*cos(azimuthal);
osRay.c._y = sin_zenith*sin(azimuthal);
osRay.c._z = sqrt(1 - sin2_zenith);
答案 3 :(得分:2)
如果您有快速RNG,这应该很快:
// RNG::draw() returns a uniformly distributed number between -1 and 1.
void drawSphereSurface(RNG& rng, double& x1, double& x2, double& x3)
{
while (true) {
x1 = rng.draw();
x2 = rng.draw();
x3 = rng.draw();
const double radius = sqrt(x1*x1 + x2*x2 + x3*x3);
if (radius > 0 && radius < 1) {
x1 /= radius;
x2 /= radius;
x3 /= radius;
return;
}
}
}
要加快速度,您可以在sqrt
区块内移动if
来电。
答案 4 :(得分:1)
我认为你遇到的结果不均匀的问题是因为在极坐标中,圆上的随机点不是均匀分布在径向轴上。如果您查看[theta, theta+dtheta]x[r,r+dr]
上的区域,对于固定的theta
和dtheta
,该区域的r
值将不同。直截了当地,中心还有更多的区域。因此,您需要缩放随机半径以解决此问题。我没有得到证明,但缩放是r=R*sqrt(rand)
,R
是圆的半径,rand
开始随机数。
答案 5 :(得分:0)
答案 6 :(得分:-1)
第一次尝试(错误)
point=[rand(-1,1),rand(-1,1),rand(-1,1)];
len = length_of_vector(point);
编辑:
怎么样?
while(1)
point=[rand(-1,1),rand(-1,1),rand(-1,1)];
len = length_of_vector(point);
if( len > 1 )
continue;
point = point / len
break
接受程度约为0.4。这意味着您将拒绝60%的解决方案。