本文探索了几种不同的在球面或半球面上采样的方法。
1. 轴向均匀采样(Uniform Axis)
最容易想到的采样方式即在三个互相垂直的坐标轴$x$,$y$,$z$上单独进行均匀采样,最后将采样到的坐标值正则化即可,不过需要注意的是该采样方式可能取到$(0,0,0)$这个坐标。伪代码如下:1
2
3
4
5
6
7do
{
x = uniform(-1, 1);
y = uniform(-1, 1);
z = uniform(-1, 1);
}while((x,y,z) == (0,0,0));
return normalize((x,y,z));
然而该方法最大的问题是其在球面上的采样非均匀,由于该方式实际上是将立方体中均匀分布的点映射到球面上,因此在立方体角点处会采样到更多的点,如下图所示:
2. 轴向正态采样 (Normal Axis)
该方法与第一种方法类似,但不是均匀采样,而是在每个轴上独立进行正态采样,然后同样地映射到半球面上即可。伪代码如下:1
2
3
4
5
6
7do
{
x = normal(-1, 1)
y = normal(-1, 1)
z = normal(-1, 1)
}while((x,y,z) == (0,0,0))
return normalize((x,y,z))
从下图可以看出,该正态采样方式是球形t对称(spherical symmetric)的,其在球面上的映射也是均匀的。
3. Marsaglia采样
该方法是一种基于变换的抽样方法,相较于方法1该方法保证了球面采样的均匀性,而与方法2比较该方法更快速,如下图所示:
根据维基百科所述,Marsaglia算法可以以较快的速度一次性生成一对相互独立的符合标准正态分布的采样。但如下的方法,本人才疏学浅,并未发现其与正态分布的联系,仅对变换的均匀性做了简单证明。
伪代码如下:1
2
3
4
5
6
7
8
9
10do
{
u = uniform(-1, 1);
v = uniform(-1, 1);
r2 = u^2 + v^2;
}while(r2 > 1);
x = 2 * u * sqrt(1 - r2);
y = 2 * v * sqrt(1 - r2);
z = 1 - 2 * r2;
return (x, y, z);
简单来说,该方法希望找到一个从圆盘$D$到球面$S$的均匀映射。因此首先在圆盘上通过rejective sampling方式进行均匀采样的到坐标$(u, v)$,然后魔幻现实主义地给出了如下映射:
4. 半球面采样
如果仅需要对半球面进行均匀采样,那么可以采用如下的变换方式:
同样地可以该出类似的证明:
基于该变换我们可以得到如下的半球面均匀采样:
5. Cos采样 (Cosine Weight Sampling)
该方法生成的点云坐标距离圆盘$D$中心越近,其密度越高。其采样方式与半球面均匀采样有细微差别:
生成的点云如下:
6. 比较
这里我比较了上述3种均匀采样的采样速度,其中实现语言使用的C++,随机数生使用的是标准库的的随机数生成器,得到了如下的结果:
采样数 | 1e4 | 1e5 | 1e6 |
---|---|---|---|
Marsaglia | 0.001s | 0.011s | 0.116s |
Normal-Axis | 0.001s | 0.029s | 0.282s |
Hemi | 0.001s | 0.008s | 0.078s |
就上述结果来看,全球面采样上,Marsaglia比正态轴向采样更快,虽然是基于rejective的方法。而半球面上的采样则比这两种方法都快速。
Reference
http://corysimon.github.io/articles/uniformdistn-on-sphere/
https://www.cnblogs.com/cofludy/p/5894270.html