在单位半球的表面上快速均匀分布的随机点

时间:2011-09-02 07:03:59

标签: c++ math random geometry

我试图在单位球体的表面上生成均匀的随机点,用于蒙特卡罗射线追踪程序。当我说均匀时,我的意思是这些点相对于表面积均匀分布。我目前的方法是计算半球上的均匀随机点,指向正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);

虽然最后两种方法比第一种方法运行的时间更快,但是当我使用它们时,我得到的结果表明它们不是在球体表面上产生均匀的随机点,而是给出了有利于赤道的分布。

此外,最后两种方法给出相同的最终结果,但我确定它们是错误的,因为我正在与分析解决方案进行比较。

我发现的每个引用都表明这些方法确实产生了均匀的分布,但是我没有达到正确的结果。

我的实施中是否有错误,或者我在第二和第三种方法中错过了一个基本想法?

7 个答案:

答案 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]上的区域,对于固定的thetadtheta,该区域的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%的解决方案。