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

C++ 单位半球面上的快速均匀分布随机点,c++,math,random,geometry,C++,Math,Random,Geometry,我正试图为蒙特卡罗光线追踪程序在单位球体的表面上生成均匀的随机点。当我说均匀时,我的意思是,这些点相对于表面积是均匀分布的。我目前的方法是计算指向正z轴和底面在x-y平面的半球上的均匀随机点 半球上的随机点表示漫反射灰色发射器的热辐射发射方向 当我使用以下计算时,我得到了正确的结果: 注意:dsfmt*is将返回一个介于0和1之间的随机数。 azimuthal = 2*PI*dsfmt_genrand_close_open(&dsfmtt); zenith = asin(sqrt(dsf

我正试图为蒙特卡罗光线追踪程序在单位球体的表面上生成均匀的随机点。当我说均匀时,我的意思是,这些点相对于表面积是均匀分布的。我目前的方法是计算指向正z轴和底面在x-y平面的半球上的均匀随机点

半球上的随机点表示漫反射灰色发射器的热辐射发射方向

当我使用以下计算时,我得到了正确的结果:

注意:dsfmt*is将返回一个介于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);
然而,这相当慢,并且分析表明它占用了运行时间的很大一部分。因此,我找到了一些替代方法:

Marsaglia 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);
虽然后两种方法的运行速度比第一种快很多倍,但当我使用它们时,我得到的结果表明,它们并没有在球体表面生成均匀的随机点,而是给出了有利于赤道的分布

此外,最后两种方法给出了相同的最终结果,但我确信它们是不正确的,因为我正在与解析解进行比较

我找到的每一个参考文献都表明,这些方法确实产生了均匀分布,但是我没有得到正确的结果


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

我认为非均匀结果的问题在于,在极坐标系中,圆上的随机点在径向轴上的分布不均匀。如果查看
[theta,theta+dtheta]x[r,r+dr]
上的区域,对于固定
theta
dtheta
,区域将因
r
的不同值而不同。直观地说,在离中心更远的地方有“更多的区域”。因此,您需要缩放随机半径来考虑这一点。我还没有找到证据,但是比例是
r=r*sqrt(rand)
,其中
r
是圆的半径,
rand
开始随机数。

你试过摆脱
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中,e^(-x^2/2)e^(-y^2/2)e^(-z^2/2)=e^(-x^2+y^2+z^2)/2),因此关节分布通过旋转保持不变

如果您使用快速正态分布生成器(Ziggurat或制服比率)和快速标准化例程(google表示“快速平方根逆”),则速度很快。不需要超越函数调用

此外,Marsaglia在半球上并不均匀。由于半球上2D圆盘点上的对应点不是等距的,因此赤道附近会有更多点。最后一个点似乎是正确的(但我没有进行计算以确保这一点)。

第一次尝试(错误)

编辑:

那怎么办

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%的解决方案。

如果您有快速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;
        }
    }   
}
//RNG::draw()返回一个在-1和1之间均匀分布的数字。
空心拉丝球表面(RNG&RNG、double&x1、double&x2、double&x3)
{
while(true){
x1=rng.draw();
x2=rng.draw();
x3=rng.draw();
常数双半径=sqrt(x1*x1+x2*x2+x3*x3);
如果(半径>0&&radius<1){
x1/=半径;
x2/=半径;
x3/=半径;
返回;
}
}   
}

为了加快速度,您可以将
sqrt
调用移动到
if
块内部。

如果您对高度为
h
的单位球体进行水平切片,其表面积仅为
2pih
(这是阿基米德计算球体表面积的方式)。因此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(方位角)
sin(方位角)
也可以节省一些时间--请参阅以了解讨论


编辑以添加:好的,我现在知道这只是对第三种方法的一个小小的调整。但它减少了一个步骤。

第二种和第三种方法实际上使用第二种方法在球体表面上生成均匀分布的随机点()在Intel Xeon 2.8 GHz四核上以大约两倍的速度提供最快的运行时间

正如前面提到的,还有一种使用正态分布的方法,它比我介绍的方法更好地扩展到n个球体

将为您提供有关在球体表面上选择均匀分布的随机点的详细信息


TonyK指出,我的初始方法不会生成均匀分布的点,而是在生成随机点时偏移极点。这是我试图解决的问题所要求的,但我只是假设它会生成均匀随机点。正如该方法所建议的,可以通过移除asin()来优化调用以将运行时间减少20%左右。

您需要什么样的精度?使用第一个算法并探索加速三角函数的方法可能是最快的。例如,您可以预先计算正弦和余弦表,然后使用某种快速查找和插值,而不是全精度计算。但是,我不确定舍入误差会如何影响结果。我曾考虑过使用快速触发函数,但我担心模拟对角度相当敏感。预计算表格
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);