一、简介
本文介绍了如何在半球表面上进行半球面上的均匀采样
、半球面上的cos加权采样
。
在介绍半球面上的cos加权采样
时,介绍了 在球面上采样(方法一)和在投影圆内采样(方法二)两种方法。
并且给出了相关公式推导和python代码实现。
二、在半球上采样
0.预备知识
1).球面坐标系与笛卡尔坐标系
在半球面上采样时,常使用球面坐标系
。先采样球面坐标系
下的坐标参数
(
ϕ
,
θ
)
(\phi,\theta)
(ϕ,θ),然后转换为笛卡尔坐标系
参数
(
x
,
y
,
z
)
(x,y,z)
(x,y,z)。两个坐标系下的参数示意图如下:
笛卡尔坐标系
示意图:
球面坐标系
示意图:
从球面坐标系
转为笛卡尔坐标系
的公式如下:
x
=
c
o
s
(
ϕ
)
×
s
i
n
(
θ
)
y
=
c
o
s
(
θ
)
z
=
s
i
n
(
ϕ
)
×
s
i
n
(
θ
)
(1)
x = cos(\phi) \times sin(\theta) \\ y = cos(\theta) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ z = sin(\phi) \times sin(\theta)\tag{1}
x=cos(ϕ)×sin(θ)y=cos(θ) z=sin(ϕ)×sin(θ)(1)
在单位球面坐标系上的立体角微元等于:
d
ω
=
d
A
r
2
=
d
A
(2)
\rm{d}\omega = \frac{\rm{d}A}{r^2}=\rm{d}A \tag{2}
dω=r2dA=dA(2)
在单位球面上的面积微元
d
A
\rm{d}A
dA等于:
d
A
=
r
2
×
s
i
n
(
θ
)
d
θ
d
ϕ
=
s
i
n
(
θ
)
d
θ
d
ϕ
(3)
\rm{d}A=r^2\times sin(\theta)\rm{d}\theta\rm{d}\phi=sin(\theta)\rm{d}\theta\rm{d}\phi \tag{3}
dA=r2×sin(θ)dθdϕ=sin(θ)dθdϕ(3)
2).逆变换采样
逆变换采样是伪随机数采样的一种基本方法,在已知任意概率分布函数(probability distribution function, PDF)
的累计分布函数(cumulative distribution function, CDF)
时,可以从该PDF
中生成随机样本。本文只简单介绍逆变换采样的步骤和示例,对其推导过程和原理不做过多介绍。
使用逆变换采样方法的步骤如下:
- (1).根据已知的PDF函数 f ( x ) = y f(x)=y f(x)=y,计算对应的CDF函数 F ( X ) F(X) F(X);
- (2).根据 CDF函数 F ( X ) F(X) F(X) 求出其反函数 F − 1 ( Y ) F^{-1}(Y) F−1(Y);
- (3).在[0,1]上均匀采样随机变量 u u u;
- (4).那么 F − 1 ( u ) F^{-1}(u) F−1(u)即为满足PDF函数 f ( x ) = y f(x)=y f(x)=y的随机变量。
示例:
已知PDF函数为 f ( x ) = 1 2 x exp ( − x ) , x ≥ 0 f(x)=\frac{1}{2\sqrt{x}}\exp(-\sqrt{x}), x\geq 0 f(x)=2x1exp(−x),x≥0,求采样得到满足 f ( x ) f(x) f(x)分布的随机变量。
- (1).PDF为 f ( x ) = 1 2 x exp ( − x ) , x ≥ 0 f(x)=\frac{1}{2\sqrt{x}}\exp(-\sqrt{x}), x\geq 0 f(x)=2x1exp(−x),x≥0,那么CDF为 F ( X ) = 1 − e x p ( − X ) F(X)=1-exp(-\sqrt{X}) F(X)=1−exp(−X);
- (2).CDF对应的逆函数为 F − 1 ( Y ) = ( l o g ( 1 − Y ) ) 2 F^{-1}(Y)=(log(1-Y))^2 F−1(Y)=(log(1−Y))2;
- (3).在[0,1]上均匀采样随机变量 u u u;
- (4).那么随机变量 x ′ = F − 1 ( u ) = ( l o g ( 1 − u ) ) 2 x'=F^{-1}(u)=(log(1-u))^2 x′=F−1(u)=(log(1−u))2即满足目标PDF函数;
1.在半球面上均匀采样 uniform sample
对于半球面上的均匀采样,PDF应与微元面积成正比,那么对于
ϕ
\phi
ϕ和
θ
\theta
θ的累计密度函数应该为:
C
D
F
(
ϕ
)
=
∫
0
ϕ
∫
0
π
/
2
s
i
n
(
θ
)
d
θ
d
ϕ
∫
0
2
π
∫
0
π
/
2
s
i
n
(
θ
)
d
θ
d
ϕ
=
∫
0
ϕ
∫
0
π
/
2
s
i
n
(
θ
)
d
θ
d
ϕ
2
π
=
ϕ
2
π
C
D
F
(
θ
)
=
∫
0
2
π
∫
0
θ
s
i
n
(
θ
)
d
θ
d
ϕ
∫
0
2
π
∫
0
π
/
2
s
i
n
(
θ
)
d
θ
d
ϕ
=
∫
0
2
π
∫
0
θ
s
i
n
(
θ
)
d
θ
d
ϕ
2
π
=
1
−
c
o
s
(
θ
)
(4)
CDF(\phi)=\frac{\int_{0}^{\phi}\int_{0}^{\pi/2}sin(\theta)\rm{d}\theta\rm{d}\phi}{\int_{0}^{2\pi}\int_{0}^{\pi/2}sin(\theta)\rm{d}\theta\rm{d}\phi}=\frac{\int_{0}^{\phi}\int_{0}^{\pi/2}sin(\theta)\rm{d}\theta\rm{d}\phi}{2\pi}=\frac{\phi}{2\pi} \\ CDF(\theta)=\frac{\int_{0}^{2\pi}\int_{0}^{\theta}sin(\theta)\rm{d}\theta\rm{d}\phi}{\int_{0}^{2\pi}\int_{0}^{\pi/2}sin(\theta)\rm{d}\theta\rm{d}\phi}=\frac{\int_{0}^{2\pi}\int_{0}^{\theta}sin(\theta)\rm{d}\theta\rm{d}\phi}{2\pi}=1-cos(\theta) \tag{4}
CDF(ϕ)=∫02π∫0π/2sin(θ)dθdϕ∫0ϕ∫0π/2sin(θ)dθdϕ=2π∫0ϕ∫0π/2sin(θ)dθdϕ=2πϕCDF(θ)=∫02π∫0π/2sin(θ)dθdϕ∫02π∫0θsin(θ)dθdϕ=2π∫02π∫0θsin(θ)dθdϕ=1−cos(θ)(4)
对应的逆函数分别为:
C
D
F
−
1
(
ϕ
′
)
=
2
π
×
ϕ
′
C
D
F
−
1
(
θ
′
)
=
a
r
c
c
o
s
(
1
−
θ
′
)
(5)
CDF^{-1}(\phi') = 2\pi \times \phi' \\ CDF^{-1}(\theta') = arccos(1-\theta') \tag{5}
CDF−1(ϕ′)=2π×ϕ′CDF−1(θ′)=arccos(1−θ′)(5)
之后在[0,1]之间均匀采样随机变量
u
1
,
u
2
u1,u2
u1,u2,然后令:
ϕ
=
C
D
F
−
1
(
u
1
)
=
2
π
×
u
1
θ
=
C
D
F
−
1
(
u
2
)
=
a
r
c
o
s
(
1
−
u
2
)
(6)
\phi = CDF^{-1}(u1)=2\pi \times u1 \\ \theta = CDF^{-1}(u2)=arcos(1-u2) \tag{6}
ϕ=CDF−1(u1)=2π×u1θ=CDF−1(u2)=arcos(1−u2)(6)
根据上式计算得到的
(
ϕ
,
θ
)
(\phi,\theta)
(ϕ,θ)就满足在半球面上按照面积均匀分布。
下面是采样python代码和结果(需要注意代码中笛卡尔坐标系的Y轴和Z轴做了交换):
代码:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def uniform_sample_hemisphere(n_samples):
points = []
for _ in range(n_samples):
u1 = np.random.rand()
u2 = np.random.rand()
phi = 2 * np.pi * u1 # 计算方位角
theta = np.arccos(1.0-u2) # 计算极角
# 转换到笛卡尔坐标系
x = np.sin(theta) * np.cos(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(theta)
points.append((x, y, z))
return np.array(points)
# 生成样本
n_samples = 5000
samples = uniform_sample_hemisphere(n_samples)
# 3D 可视化
fig = plt.figure(figsize=(12, 6))
# 绘制 3D 采样
ax1 = fig.add_subplot(121, projection='3d')
ax1.scatter(samples[:, 0], samples[:, 1], samples[:, 2], alpha=0.6, s=1)
ax1.set_xlabel('X axis')
ax1.set_ylabel('Y axis')
ax1.set_zlabel('Z axis')
ax1.set_title('Uniform Sampling on Hemisphere')
# 绘制 x-y 平面映射
ax2 = fig.add_subplot(122)
ax2.scatter(samples[:, 0], samples[:, 1], alpha=0.6, s=1)
ax2.set_xlabel('X axis')
ax2.set_ylabel('Y axis')
ax2.set_title('Projection onto XY Plane')
ax2.axis('equal')
plt.tight_layout()
plt.show()
结果:
2.在半球面上的cos加权采样:方法一
对于半球面上的cos加权采样,PDF应与
微元面积
×
c
o
s
(
θ
)
微元面积\times cos(\theta)
微元面积×cos(θ)成正比,即在
θ
\theta
θ越小的区域采样概率越大,在
θ
\theta
θ越大的区域概率越小,那么对于
ϕ
\phi
ϕ和
θ
\theta
θ的累计密度函数应该为:
C
D
F
(
ϕ
)
=
∫
0
ϕ
∫
0
π
/
2
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
∫
0
2
π
∫
0
π
/
2
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
=
∫
0
ϕ
∫
0
π
/
2
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
π
=
ϕ
/
2
π
=
ϕ
2
π
C
D
F
(
θ
)
=
∫
0
2
π
∫
0
θ
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
∫
0
2
π
∫
0
π
/
2
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
=
∫
0
2
π
∫
0
θ
c
o
s
(
θ
)
s
i
n
(
θ
)
d
θ
d
ϕ
π
=
π
×
s
i
n
2
(
θ
)
π
=
s
i
n
2
(
θ
)
=
1
−
c
o
s
2
(
θ
)
(7)
CDF(\phi)=\frac{\int_{0}^{\phi}\int_{0}^{\pi/2}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}{\int_{0}^{2\pi}\int_{0}^{\pi/2}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}=\frac{\int_{0}^{\phi}\int_{0}^{\pi/2}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}{\pi}=\frac{\phi/2}{\pi}=\frac{\phi}{2\pi} \\ CDF(\theta)=\frac{\int_{0}^{2\pi}\int_{0}^{\theta}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}{\int_{0}^{2\pi}\int_{0}^{\pi/2}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}=\frac{\int_{0}^{2\pi}\int_{0}^{\theta}cos(\theta)sin(\theta)\rm{d}\theta\rm{d}\phi}{\pi}=\frac{\pi \times sin^{2}(\theta)}{\pi} = sin^{2}(\theta)=1-cos^2(\theta) \tag{7}
CDF(ϕ)=∫02π∫0π/2cos(θ)sin(θ)dθdϕ∫0ϕ∫0π/2cos(θ)sin(θ)dθdϕ=π∫0ϕ∫0π/2cos(θ)sin(θ)dθdϕ=πϕ/2=2πϕCDF(θ)=∫02π∫0π/2cos(θ)sin(θ)dθdϕ∫02π∫0θcos(θ)sin(θ)dθdϕ=π∫02π∫0θcos(θ)sin(θ)dθdϕ=ππ×sin2(θ)=sin2(θ)=1−cos2(θ)(7)
对应的逆函数分别为:
C
D
F
−
1
(
ϕ
′
)
=
2
π
×
ϕ
′
C
D
F
−
1
(
θ
′
)
=
a
r
c
c
o
s
(
1
−
θ
′
)
(8)
CDF^{-1}(\phi') = 2\pi \times \phi' \\ CDF^{-1}(\theta') = arccos(\sqrt{1-\theta'}) \tag{8}
CDF−1(ϕ′)=2π×ϕ′CDF−1(θ′)=arccos(1−θ′)(8)
之后在[0,1]之间均匀采样随机变量
u
1
,
u
2
u1,u2
u1,u2,然后令:
ϕ
=
C
D
F
−
1
(
u
1
)
=
2
π
×
u
1
θ
=
C
D
F
−
1
(
u
2
)
=
a
r
c
o
s
(
1
−
u
2
)
(9)
\phi = CDF^{-1}(u1)=2\pi \times u1 \\ \theta = CDF^{-1}(u2)=arcos(\sqrt{1-u2}) \tag{9}
ϕ=CDF−1(u1)=2π×u1θ=CDF−1(u2)=arcos(1−u2)(9)
根据上式计算得到的
(
ϕ
,
θ
)
(\phi,\theta)
(ϕ,θ)就满足在半球面上按照
微元面积
×
c
o
s
(
θ
)
微元面积\times cos(\theta)
微元面积×cos(θ)加权分布。
下面是采样python代码和结果(需要注意代码中笛卡尔坐标系的Y轴和Z轴做了交换):
代码:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def cos_weighted_sample_hemisphere(n_samples):
points = []
for _ in range(n_samples):
u1 = np.random.rand()
u2 = np.random.rand()
phi = 2 * np.pi * u1 # 计算方位角
theta = np.arccos(np.sqrt(1 - u2)) # 计算极角
# 转换到笛卡尔坐标系
x = np.sin(theta) * np.cos(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(theta)
points.append((x, y, z))
return np.array(points)
# 生成样本
n_samples = 5000
samples = cos_weighted_sample_hemisphere(n_samples)
# 3D 可视化
fig = plt.figure(figsize=(12, 6))
# 绘制 3D 采样
ax1 = fig.add_subplot(121, projection='3d')
ax1.scatter(samples[:, 0], samples[:, 1], samples[:, 2], alpha=0.6, s=1)
ax1.set_xlabel('X axis')
ax1.set_ylabel('Y axis')
ax1.set_zlabel('Z axis')
ax1.set_title('Cos-Weighted Sampling on Hemisphere')
# 绘制 x-y 平面映射
ax2 = fig.add_subplot(122)
ax2.scatter(samples[:, 0], samples[:, 1], alpha=0.6, s=1)
ax2.set_xlabel('X axis')
ax2.set_ylabel('Y axis')
ax2.set_title('Projection onto XY Plane')
ax2.axis('equal')
plt.tight_layout()
plt.show()
结果:
3.在半球面上的cos加权采样:方法二
另一种对于半球面上的cos加权采样
的方法如下:
直接在X-Z平面的圆内均匀采样点,然后将采样的点映射到上方的球面上,如下如图所示:
在采样时可以使用接受拒绝采样算法
,在一个的大小为1*1的正方形内均匀采样,然后只保留距离圆心小于等于1的采样点。
或者使用前面逆变换采样
方法,分别采样半径
r
r
r和角度
ϕ
\phi
ϕ。其中采样角度
ϕ
\phi
ϕ的过程与前面相同。采样半径
r
r
r时,先计算
r
r
r的CDF。因为在圆面内面积微元
d
A
\rm{d}A
dA为:
d
A
=
r
∗
d
r
d
ϕ
(10)
\rm{d}A=r*\rm{d}r\rm{d}{\phi} \tag{10}
dA=r∗drdϕ(10)
在圆平面内均匀采样一点,那么采样点对应的半径
r
r
r的
C
D
F
(
r
)
CDF(r)
CDF(r)等于:
C
D
F
(
r
)
=
∫
0
2
π
∫
0
r
r
d
r
d
ϕ
∫
0
2
π
∫
0
1
r
d
r
d
ϕ
=
2
π
∗
r
2
/
2
π
=
r
2
(11)
CDF(r)=\frac{\int_{0}^{2\pi}\int_{0}^{r}r\rm{d}r\rm{d}\phi}{\int_{0}^{2\pi}\int_{0}^{1}r\rm{d}r\rm{d}\phi}=\frac{2\pi*r^{2}/2}{\pi}=r^2 \tag{11}
CDF(r)=∫02π∫01rdrdϕ∫02π∫0rrdrdϕ=π2π∗r2/2=r2(11)
C
D
F
−
1
(
u
)
=
u
(12)
CDF^{-1}(u)=\sqrt{u} \tag{12}
CDF−1(u)=u(12)
因此可以在[0,1]上均匀采样随机变量
u
u
u,那么根据公式(12),令
r
=
u
r=\sqrt{u}
r=u。根据此方法得到的采样点
(
r
,
ϕ
)
(r,\phi)
(r,ϕ)在圆平面内即为均匀分布的。
证明:
接下来将证明本方法与在半球面上的cos加权采样:方法一
的等效性。
我们将目标放在X-Z平面上的圆平面内,在该圆面内可以使用极坐标
(
r
,
ϕ
)
(r,\phi)
(r,ϕ)表示任意一点。
因为:
C
D
F
(
θ
)
=
P
D
F
(
Θ
≤
θ
)
CDF(\theta)=PDF(\Theta\leq\theta)
CDF(θ)=PDF(Θ≤θ)
同时在极坐标中:
r
=
s
i
n
(
θ
)
r=sin(\theta)
r=sin(θ),即
R
=
s
i
n
(
Θ
)
R=sin(\Theta)
R=sin(Θ)
那么:
P
D
F
(
Θ
≤
θ
)
=
P
D
F
(
R
≤
s
i
n
(
θ
)
)
PDF(\Theta\leq\theta)=PDF(R\leq sin(\theta))
PDF(Θ≤θ)=PDF(R≤sin(θ))
又因为:
r
=
s
i
n
(
θ
)
r=sin(\theta)
r=sin(θ)
因此:
P
D
F
(
R
≤
s
i
n
(
θ
)
)
=
P
D
F
(
R
≤
r
)
=
C
D
F
(
r
)
=
r
2
=
s
i
n
2
(
θ
)
PDF(R\leq sin(\theta))=PDF(R\leq r)=CDF(r)=r^2=sin^2(\theta)
PDF(R≤sin(θ))=PDF(R≤r)=CDF(r)=r2=sin2(θ)
即:
C
D
F
(
θ
)
=
s
i
n
2
(
θ
)
CDF(\theta)=sin^{2}(\theta)
CDF(θ)=sin2(θ)
即映射点
(
θ
,
ϕ
)
(\theta,\phi)
(θ,ϕ)中的坐标值
θ
\theta
θ,满足
C
D
F
(
θ
)
=
s
i
n
2
(
θ
)
CDF(\theta)=sin^{2}(\theta)
CDF(θ)=sin2(θ),而这正是公式(7)中我们想要找到一个随机变量
θ
\theta
θ。
同理可得,使用该方法得到的映射点坐标
ϕ
\phi
ϕ也满足公式(7)中
C
D
F
(
ϕ
)
=
ϕ
2
π
CDF(\phi)=\frac{\phi}{2\pi}
CDF(ϕ)=2πϕ
下面是采样python代码和结果(需要注意代码中笛卡尔坐标系的Y轴和Z轴做了交换):
代码:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 生成 cos 加权采样的函数
def cos_weighted_sample(n_samples):
points = []
for _ in range(n_samples):
u1 = np.random.rand()
u2 = np.random.rand()
r = np.sqrt(u1) # 均匀采样半径
phi = 2 * np.pi * u2 # 计算方位角 # 均匀采样角度
x = r * np.cos(phi)
y = r * np.sin(phi)
z = np.sqrt(1 - x**2 - y**2)
if (x*x+y*y <= 1.0): # 只保留圆内的点
points.append((x, y, z))
return np.array(points)
# 生成样本
n_samples = 5000
samples = cos_weighted_sample(n_samples)
# 3D 可视化
fig = plt.figure(figsize=(12, 6))
# 绘制 3D 采样
ax1 = fig.add_subplot(121, projection='3d')
ax1.scatter(samples[:, 0], samples[:, 1], samples[:, 2], alpha=0.6, s=1)
ax1.set_xlabel('X axis')
ax1.set_ylabel('Y axis')
ax1.set_zlabel('Z axis')
ax1.set_title('Cos-Weighted Sampling on Hemisphere')
# 绘制 x-y 平面映射
ax2 = fig.add_subplot(122)
ax2.scatter(samples[:, 0], samples[:, 1], alpha=0.6, s=1)
ax2.set_xlabel('X axis')
ax2.set_ylabel('Y axis')
ax2.set_title('Projection onto XY Plane')
ax2.axis('equal')
plt.tight_layout()
plt.show()