NeRF工作流程梳理
主要内容为学习过程中的记录与梳理,强烈建议阅读原论文和链接下的内容。
参考链接:
http://139.9.1.231/index.php/2023/01/03/nerf-code/
https://zhuanlan.zhihu.com/p/595117334
https://bbs.huaweicloud.com/forum/thread-192835-1-1.html
https://zhuanlan.zhihu.com/p/636233715 (NeRF采样策略)
https://blog.csdn.net/cpu077/article/details/122746009?spm=1001.2014.3001.5502
NeRF学习过程中的记录,参考多篇文章,联系侵删。
1. NeRF工作流程
在 NeRF 中,作者做了一个这样的尝试,利用神经网络去学习体渲染中粒子状态,而事实证明,这是种方式的效果相当好。
整个过程是这样的:
给定相机位置和朝向后,我们可以确定出当前的成像平面。然后,将相机的位置坐标和平面上的某个像素相连,就确定了一条光线 (也即确定了光线的方向)。接着用网络预测出光线上每个采样点的粒子信息,就可以确定像素颜色。这个过程重复下去,直到每个像素都渲染完为止。
这些排列整齐的光线,构成了类似磁场一样的东西,而光线本身就是一种辐射,因此叫辐射场。而每条光线上的粒子信息又都是由神经网络预测的,因此作者又给整个过程命名为神经辐射场。
NeRF具体实现流程
NeRF实现的具新视角合成的工作流程如下:
- 输入多视角图片(包括像素坐标、像素颜色),以及相机内参、位姿等数据;
- 根据光线步进法产生射线,用随机采样和重要性采样得到空间采样点的坐标;
- 将空间采样点的坐标以及射线的视角进行位置编码输入到NeRF网络中,得到网络对于空间点 R G B σ RGB\sigma RGBσ值的预测;
- 根据空间点的 R G B σ RGB\sigma RGBσ值,用体绘制原理,渲染出射线对应的二维像素点的 R G B RGB RGB;
- 将预测、渲染得到的像素点的 R G B RGB RGB,与ground truth做MSE loss,训练神经网络。
总结起来NeRF就是利用神经辐射场隐式的得到了整个三维场景信息,然后再利用体渲染的方式合成了整个三维场景。知道了整个工作流程,下面将从网络结构开始,从分析网络结构,到网络的输入和输出,了解关于NeRF的整个细节。
2. 光线步进法
NeRF使用MLP隐式重建三维场景时的输入是采样点的位姿,NeRF的目标是实现2D新视角图像生成,那么要怎么得到采样点的位姿,又怎么使用重建得到的3D密度和颜色呢,得到新视角下的2D图像?
为了处理这两个问题,NeRF使用了光线步进(Ray Marching)这一经典方法,设 o \boldsymbol{o} o代表相机原点 O c O_c Oc在世界坐标系中的位置, d \boldsymbol{d} d代表射线的单位方向矢量, t t t代表从 O c O_c Oc出发,沿射线方向行进的距离。使用光线步进法时,2D图像的每个像素对应于一条射线,每条射线上的任意位置可以表示为 r ( t ) = o + t d \boldsymbol{r}(t)=\boldsymbol{o}+t\boldsymbol{d} r(t)=o+td,对这些射线进行采样(具体使用下文介绍的随机采样和重要性采样)即可得到采样点的位姿,对这些射线上的采样点进行积分(具体介绍的体绘制方法)即可得到2D像素的RGB值。
3. 分层采样
NeRF使用随机采样和重要性采样结合的方式在光线步进法生成的射线上进行采样,这是因为空间中的物体分布是稀疏的,一条射线上可能只有很小的一段区域是对最终渲染起作用的,如果用均匀采样会浪费很多采样点,网络也难以学到整个连续空间中的分布,所以采用coarse to fine的思想,构建粗采样网络和细采样网络可以更好的对空间进行采样。(coarse to fine网络是啥)
**随机采样:**将射线从近场到远场的范围 [ t n , t f ] [t_n,t_f] [tn,tf]均匀划分成 N c N_c Nc个区间,在每段区间内随机取一个点,将其空间坐标 r \boldsymbol{r} r和空间视角 d \boldsymbol{d} d输入粗采样网络,得到粗采样网络预测的该空间点的 R G B σ RGB\sigma RGBσ。
**重要性采样:**粗采样网络的输出中包括一条射线上所有点的权重 w i w_i wi,将其归一化后作为采样区间的概率密度函数PDF,按照概率密度函数随机采样 N f N_f Nf个点,与前面分段均匀采样的 N c N_c Nc个点合并后输入细采样网络。
分层采样与体素渲染是分不开的,在第8节得到离散化渲染的公式,会有详细说明。
NeRF将粗采样网络和细采样网络的渲染结果(2D像素点的RGB值),分别与ground truth计算均方误差,将两者之和作为总的loss,来同时训练两个网络。
4. 位置编码
输入到网络的数据会经过
(
x
,
y
,
z
,
θ
,
ϕ
)
(x,y,z,\theta,\phi)
(x,y,z,θ,ϕ) -->63维高维特征,这里使用了
r
(
⋅
)
r(·)
r(⋅)函数进行了位置变换:
r
(
p
)
=
(
s
i
n
(
2
0
π
p
)
,
c
o
s
(
2
0
π
p
)
,
.
.
.
,
s
i
n
(
2
L
−
1
π
p
)
,
c
o
s
(
2
L
−
1
π
p
)
)
(1)
r(p) = (sin(2^0{\pi}p), cos(2^0{\pi}p),..., sin(2^{L-1}{\pi}p), cos(2^{L-1}{\pi}p)) \tag{1}
r(p)=(sin(20πp),cos(20πp),...,sin(2L−1πp),cos(2L−1πp))(1)
式(1)中的
p
p
p 表示输入的位置信息或者是观测角度信息,
L
L
L表示的是每个编码的数量,对于位置编码论文设置
L
=
10
L=10
L=10,对于观测角度
L
=
4
L=4
L=4,输入网络中的63和27就是通过位置编码进行的维度提升(这里的63是因为增加了原来的值,但是在之后的其他论文里面有去掉原来的值,对最终的结果也没有影响,所以在一些网络结构图里面会看到60的情况)。对于每个点的位置坐标
(
x
,
y
,
z
)
(x,y,z)
(x,y,z) 3个值,都使用10个
s
i
n
sin
sin和10个
c
o
s
cos
cos进行扩展,同时再连接一个自身,即
(
x
,
s
i
n
(
2
0
π
p
)
,
c
o
s
(
2
0
π
p
)
,
.
.
.
,
s
i
n
(
2
L
−
1
π
p
)
,
c
o
s
(
2
L
−
1
π
p
)
)
(x, sin(2^0{\pi}p), cos(2^0{\pi}p),..., sin(2^{L-1}{\pi}p), cos(2^{L-1}{\pi}p))
(x,sin(20πp),cos(20πp),...,sin(2L−1πp),cos(2L−1πp)),因此每个值都拓展为10+10+1=21维,对于3个位置坐标,一共为
(
10
+
10
+
1
)
×
3
=
63
(10+10+1)×3=63
(10+10+1)×3=63维;同理,对于观测角度位置编码计算
L
=
4
L=4
L=4,即利用4个
s
i
n
sin
sin和4个
c
o
s
cos
cos进行拓展,这里输入保留了一位,实际输入是
(
θ
,
ϕ
,
1
)
(θ,ϕ,1)
(θ,ϕ,1)。即3个观察角角度信息拓展为
(
4
+
4
+
1
)
×
3
=
27
(4+4+1)×3=27
(4+4+1)×3=27维。至于NeRF为什么要引入位置编码,从实验结果上看,不使用位置编码会导致生成的图像更模糊(平滑),而引入了位置编码能够提升生成图像的清晰度。
5. NeRF网络结构部分
回顾一下,NeRF的数据输入是静态场景下大量包含相机参数并且已知相机所处位置 ( x , y , z ) (x,y,z) (x,y,z)和相机朝向 ( θ , ϕ ) (\theta,\phi) (θ,ϕ)的与图片相关的训练集,即实际的输入是 ( x , y , z , θ , ϕ ) (x,y,z,\theta,\phi) (x,y,z,θ,ϕ)这5维向量,但是网络实际输入的通道数是63,这里就涉及到了位置编码问题,有关位置编码后面再讲,上面MLP网络主要通过全连接层(MLP)构建,在第5层位置还会再次concat最开始经过位置编码输入的位置信息(相当于ResNet网络的skip connection) 之后再经过4层全连接层256维特征,特征经过线下变换后(没有使用激活函数)输出体密度 σ \sigma σ。前面输出的256维线性特征会再经过线性变换与相机的角度编码特征进行concat,再经过一次全连接层和sigmod函数后输出对应位置的RGB颜色。
Nerf(
(net): ModuleList(
(0): Linear(in_features=63, out_features=256, bias=True)
(1): Linear(in_features=256, out_features=256, bias=True)
(2): Linear(in_features=256, out_features=256, bias=True)
(3): Linear(in_features=256, out_features=256, bias=True)
(4): Linear(in_features=256, out_features=256, bias=True)
(5): Linear(in_features=319, out_features=256, bias=True)
(6): Linear(in_features=256, out_features=256, bias=True)
(7): Linear(in_features=256, out_features=256, bias=True)
)
(alpha_liner): Linear(in_features=256, out_features=1, bias=True)
(feature_linear): Linear(in_features=256, out_features=256, bias=True)
(proj): Linear(in_features=283, out_features=128, bias=True)
(rgb_linear): Linear(in_features=128, out_features=3, bias=True)
)
6. 体渲染的方法
MLP网络最终输出的是体密度和RGB颜色,那么如何利用两种数据生成对应的图像呢?这就要提到体渲染(vloume rendering),NeRF 用一条射线
r
(
t
)
=
o
+
t
d
r(t)=o+td
r(t)=o+td 连接了图片中的像素点 P 和射线中的采样点,采样点在用虚线内的实黑点表示。希望求解的图片像素点 P 的RGB颜色值用符号
C
(
r
)
C(r)
C(r)表示,
C
(
r
)
C(r)
C(r)可以看作是射线从近端的采样点
t
n
t_n
tn到远端的采样点
t
f
t_f
tf的积分:
C
(
r
)
=
∫
t
n
t
f
T
(
t
)
⋅
σ
(
r
(
t
)
)
⋅
c
(
r
(
t
)
,
d
)
d
t
C(r)=\int_{t_n}^{t_f} T(t)·\sigma(r(t))·c(r(t), d)dt
C(r)=∫tntfT(t)⋅σ(r(t))⋅c(r(t),d)dt
对上面的公式进行拆解:
- r ( t ) r(t) r(t):射线的公式 r ( t ) = o + t d r(t)=o+td r(t)=o+td,t 的取值范围是从近端(near)点 t n t_n tn到远端(far)点 t f t_f tf,也就是积分的上下限;
- σ ( r ( t ) ) \sigma(r(t)) σ(r(t)): 射线在点t的体素密度值(density),由MLP的预测结果得到;
- c ( r ( t ) , d ) c(r(t), d) c(r(t),d): 射线在点 t 的 RGB 颜色 (color) 值,由 MLP 的预测结果得到;注意符号d是射线公式 r ( t ) = o + t d r(t)=o+td r(t)=o+td 里面的方向向量;
- T ( t ) T(t) T(t)是射线在点 t 的透光率 (transmittance),由体素密度 σ ( r ( t ) ) \sigma(r(t)) σ(r(t))积分得到的,是射线从 t n t_n tn和 t f t_f tf这一段路径上的累计透明度,可以被理解为一路上没有集中任何粒子的概率,具体的公式是:
T ( t ) = e x p ( − ∫ t n t σ ( r ( s ) ) d s ) T ( t ) = e x p ( − ∫ t n t σ ( r ( s ) ) d s ) T(t) = exp(-\int_{t_n}^{t} \sigma(r(s))ds)T(t) = exp(-\int_{t_n}^{t} \sigma(r(s))ds) T(t)=exp(−∫tntσ(r(s))ds)T(t)=exp(−∫tntσ(r(s))ds)
但是实际中并不能对NeRF进行连续的点的估计,所以上述的
T
(
T
)
T(T)
T(T)也就不存在,转而替换成为将射线需要积分的区域分为
N
N
N份,然后在每一个小区域中进行均匀随机采样。这样的方式能够在只采样离散点的前提下,保证采样位置的连续性。第
i
i
i个采样点可以表示为:
t
i
=
U
[
t
n
+
i
−
1
N
(
t
f
−
t
n
)
,
t
n
+
i
N
(
t
f
−
t
n
)
]
t_i=U[t_n+\frac{i-1}{N}(t_f-t_n), t_n+\frac{i}{N}(t_f-t_n)]
ti=U[tn+Ni−1(tf−tn),tn+Ni(tf−tn)]
于是离散化渲染的公式就变成了原论文的式(3):
多层级体素采样渲染
Nerf的渲染过程计算量很大,每条射线都要采样很多店。但是实际上,一条射线上大部分是空白区域,或者是被遮挡的区域,因此作者采用了一种”coarse to fine"的形式,同时优化coarse网络和fine网络。
对于coarse网络,我们可以采样较为记住的Nc个点,并将前述的离散求和函数重新表示为:
C
c
(
r
)
^
=
∑
i
=
1
N
ω
i
c
i
\hat{C_c(r)}=\sum^N_{i=1}\omega_ic_i
Cc(r)^=i=1∑Nωici
其中
w
i
=
T
i
(
1
−
e
x
p
(
−
σ
i
⋅
δ
i
)
)
w_i=T_i(1-exp(-\sigma_i·\delta_i))
wi=Ti(1−exp(−σi⋅δi))
接下来可以对
w
w
w做归一化:
w
i
^
=
w
i
∑
j
=
1
N
c
w
j
\hat{w_i}=\frac{w_i}{\sum^{N_c}_{j=1}w_j}
wi^=∑j=1Ncwjwi
此处的
w
j
w_j
wj可以看作是沿着射线的概率密度函数,具体来说就是rays_densities*(batchsize,1024,64)得到的概率密度函数,当weights比较小时就表示对当前颜色的贡献比例小。
流程的输入输出如下
1、input:体密度(ray_densities) (batchsize,1024,64,1)
2、input:颜色(rays_colors) (batchsize,1024,64,3)
3、output:features(batchsize,1024,3)
4、output:weights(batchsize,1024,64)
最终得到的 features= weights×ray_features(ray_colors)的和。
0. 体渲染模型
NeRF 的核心是基于图形学中的体渲染模型 (Volume Rendering),想了解 NeRF,读懂这个核心理论是避免不了的。
渲染可以说是图形学中的核心。所谓计算机图形学,就是让计算机模拟出一个真实的世界。而渲染,则是把这个虚拟出来的世界投影成图像,正如自然界中的各种光线经过碰撞后,投影到我们视网膜上的样子。这是现代电影和游戏中不可或缺的技术。
体渲染属于整个渲染技术的分支,它的目的主要是为了解决云、烟、果冻这类非刚性物体的渲染建模,可以简单理解为是为了处理密度较小的非固体的渲染。当然进一步推广到固体的渲染也说的通,而 nerf 也是这样做的。
为了建模这种非刚性物体的渲染,体渲染把气体等物质抽象成一团飘忽不定的粒子群。光线在穿过这类物体时,其实就是光子在跟粒子发生碰撞的过程。
下图是体渲染建模的示意图。光沿直线方向穿过一堆粒子 (粉色部分),如果能计算出每根光线从最开始发射,到最终打到成像平面上的辐射强度,我们就可以渲染出投影图像。而体渲染要做的,就是对这个过程进行建模。为了简化计算,我们就假设光子只跟它附近的粒子发生作用,这个范围就是图中圆柱体大小的区间。
体渲染把光子与粒子发生作用的过程,进一步细化为四种类型:
- 吸收 (absorption):光子被粒子吸收,会导致入射光的辐射强度减弱;
- 放射 (emission):粒子本身可能发光,比如气体加热到一定程度就会离子化,变成发光的「火焰」。这会进一步增大辐射强度;
- 外散射 (out-scattering):光子在撞击到粒子后,可能会发生弹射,导致方向发生偏移,会减弱入射光强度;
- 内散射 (in-scattering):其他方向的光子在撞到粒子后,可能和当前方向上的光子重合,从而增强当前光路上的辐射强度。
下图事四种情况的示意图,注意上图中的粉色部分箭头是视角的方向,下图中黄色箭头表示光的方向,
L
i
L_i
Li表示入射光,
L
o
L_o
Lo表示出射光。
那么由上述四种类型,可以明确在出射光和入射光之间的变化量,可以表示为4个过程的叠加:
L
o
−
L
i
=
d
L
(
x
,
w
)
=
e
m
i
s
s
i
o
n
+
i
n
s
c
a
t
t
e
r
i
n
g
−
o
u
t
s
c
a
t
t
e
r
i
n
g
−
a
b
s
o
r
p
t
i
o
n
L_o-L_i=dL(x,w)=emission+inscattering-outscattering-absorption
Lo−Li=dL(x,w)=emission+inscattering−outscattering−absorption
其中
x
x
x表示光线上某个位置点,
w
w
w表示光线发射方向。下面逐一对4个过程进行分析:
吸收过程:
假设粒子(上图中黑色小圆圈)半径是为 r r r的球体,那么每个粒子的投影面积是 A = π r 2 A=\pi r^2 A=πr2(也就是每个粒子对光线的遮挡面积 )。
假设圆柱(上图中大圆环)中粒子的密度是
ρ
\rho
ρ,,圆柱体的底面积是
E
E
E。当圆柱体足够薄 (薄到跟粒子一样厚) 的时候,可以认为粒子之间不会互相重叠 (也就是粒子都平铺在圆柱体一个横截面上)。假定这个厚度是
Δ
s
\Delta s
Δs,那么在这个厚度内,圆柱体体积为
E
Δ
s
E\Delta s
EΔs,粒子总数为
ρ
E
Δ
s
\rho E \Delta s
ρEΔs。这些粒子的遮挡面积为
ρ
E
Δ
s
A
\rho E \Delta s A
ρEΔsA, 占整个底面积的比例为:
ρ
E
Δ
s
A
E
=
ρ
Δ
s
A
\frac{\rho E \Delta sA}{E}=\rho \Delta sA
EρEΔsA=ρΔsA,也就是说有一束光通过这个圆柱体的时候,有
ρ
Δ
s
A
\rho \Delta sA
ρΔsA的概率会被遮挡。
换句话说,如果我们在圆柱体的一端发射无数光线 (假设都朝相同的方向),在另一端接收,会发现有些光线安然通过,有些则被粒子遮挡 (吸收)。但可以确定的是,这些接受到的光线总强度,相比入射光线总强度而言,会有
ρ
Δ
s
A
\rho \Delta sA
ρΔsA比例的衰减,即出射光的强度均值是入射光的
ρ
Δ
s
A
\rho \Delta sA
ρΔsA 倍。数学上可以表示为:
I
o
−
I
i
=
Δ
I
=
−
ρ
(
s
)
Δ
s
A
I
(
s
)
(1)
I_o-I_i=\Delta I=-\rho(s) \Delta sAI(s)\ \tag{1}
Io−Ii=ΔI=−ρ(s)ΔsAI(s) (1)
注意粒子密度
ρ
\rho
ρ 是一个关于
s
s
s的函数,因为每个区域的密度都是不同的。同理
I
I
I也是类似的。(1)式可以转换为常微分方程:
Δ
I
Δ
s
=
−
ρ
(
s
)
A
I
(
s
)
=
−
τ
a
(
s
)
I
(
s
)
(2)
\frac{\Delta I}{\Delta s} =-\rho(s) AI(s)=-\tau_a(s)I(s) \tag{2}
ΔsΔI=−ρ(s)AI(s)=−τa(s)I(s)(2)
解方程可得:
I
(
s
)
=
I
0
e
x
p
(
−
∫
0
s
τ
a
(
t
)
d
t
)
(3)
I(s)=I_0exp(-\int_0^s\tau_a(t)dt) \tag{3}
I(s)=I0exp(−∫0sτa(t)dt)(3)
式(3)中的
I
0
I_0
I0表示的式常微分方程中的常数项,屋里意义上表示光线的起始点,那对于
I
o
I_o
Io来说(注意区分
I
0
I_0
I0),就应该表示成
I
o
=
I
i
e
x
p
(
−
∫
i
s
τ
a
(
t
)
d
t
)
I_o=I_iexp(-\int_i^s\tau_a(t)dt)
Io=Iiexp(−∫isτa(t)dt),那么
I
i
I_i
Ii就是
I
o
I_o
Io的起始点。公式(3)有丰富的物理意义。如果介质 (粒子群) 是均匀的,即
τ
a
(
t
)
\tau_a(t)
τa(t)处处相等,那么入射光在经过介质 (粒子群) 后,辐射强度会呈指数衰减。这被称为比尔-朗伯吸收定律 (Beer-Lambert law):
那么由此可以定义透射比(transmittance):
T
(
s
)
=
I
o
I
i
=
e
x
p
(
−
∫
i
o
τ
a
(
t
)
d
t
)
T(s)=\frac{I_o}{I_i}=exp(-\int_i^o\tau_a(t)dt)
T(s)=IiIo=exp(−∫ioτa(t)dt)
它表示粒子群某一点的透明度,数值越大,说明粒子群越透明,光线衰减的幅度就越小。而透明度本身是关于
τ
a
(
t
)
\tau_a(t)
τa(t)的方程,
τ
a
(
t
)
\tau_a(t)
τa(t) 越大,
T
(
s
)
\\T(s)
T(s)就越小。由公式 (2) 可以知道,
τ
a
(
t
)
=
ρ
(
t
)
A
\tau_a(t)=\rho(t)A
τa(t)=ρ(t)A ,它是由粒子密度和投影面积决定的。这在直觉上也很好理解,如果粒子密度大,粒子本身也比较大,那么遮住光线的概率也会相应提升,自然透明度也就下降了。
τ
a
(
t
)
\tau_a(t)
τa(t) 也被称为光学厚度 (optical depth)。
放射(emission)
除了吸收之外,粒子本身也可能发光。
假设单个粒子发射一束光的辐射强度为 I e I_e Ie,那么按照前文的描述,在圆柱体足够薄的情况下,粒子总数是 ρ E A Δ s \rho E A\Delta s ρEAΔs,则总的发光强度为 ρ E A Δ s I e \rho E A\Delta sI_e ρEAΔsIe。
如果我们在圆柱体一端去接收粒子们放射的光线,会发现有时候能接收到,有时候刚好接收点所在的光路上没有粒子,就接收不到。能接收到光线的概率为:
ρ
E
Δ
s
A
E
=
ρ
Δ
s
A
\frac{\rho E \Delta sA}{E}=\rho \Delta sA
EρEΔsA=ρΔsA
那么接收到的光线的平均强度为
ρ
Δ
s
A
I
e
\rho \Delta sA I_e
ρΔsAIe。
光在发射和吸收之间的变化如下:
Δ
I
=
ρ
(
s
)
A
I
e
(
s
)
Δ
s
\Delta I =\rho(s) AI_e(s)\Delta s
ΔI=ρ(s)AIe(s)Δs
同样地,可以得到放射光强的常微分方程:
Δ
I
Δ
s
=
ρ
(
s
)
A
I
e
(
s
)
=
τ
a
(
s
)
I
e
(
s
)
\frac{\Delta I}{\Delta s} =\rho(s) AI_e(s)=\tau_a(s)I_e(s)
ΔsΔI=ρ(s)AIe(s)=τa(s)Ie(s)
注意,
I
e
I_e
Ie 是一个关于
s
s
s的函数,因为圆柱体不同位置的粒子,所放射的光强都是有差异的。这里我们也看到,类似吸收,粒子放射的光强同样和
τ
a
(
s
)
\tau_a(s)
τa(s)有关,这在直觉上也是合理的,如果粒子能发光,那粒子密度和粒子颗粒越大,放射的辐射均值也就越大。
这个函数的求解我们放到最后再来看。
外散射(out-scattering)
粒子除了吸收光子,也可能会弹射光子,这个过程称为外散射,即光子被弹射出原本的光路,导致光线强度减弱。
同吸收一样,外散射对光线的「削弱」程度,也跟光学厚度相关,不过过程相对吸收来说又复杂一些,因此我们用 τ s \tau_s τs 来表示外散射对光线的削弱比例,以区别于 τ a \tau_a τa。
同样地,这一过程可以表示为:
Δ
I
Δ
s
=
−
τ
s
(
s
)
I
(
s
)
\frac{\Delta I }{\Delta s}=-\tau_s(s)I(s)
ΔsΔI=−τs(s)I(s)
内散射(in-scattering)
光子可以被弹射走,自然就有其他光路的光子被弹射到当前光路,这一过程就是内散射。
内散射的过程比外散射又更加复杂,因为弹射到当前光路的光子可能来自多条不同的光路,因此需要综合考虑其他光路的辐射强度以及各种弹射角度。
不过本文不打算深入探究这一点,就认为其他光路的辐射强度为 I s I_s Is,而弹射到当前光路的能量损失比为 τ s \tau_s τs (注意这里和外散射使用的是相同的系数,既然都是散射,那有些性质上自然是共通的)。
内散射的过程可以表示为:
Δ
I
Δ
s
=
τ
s
(
s
)
I
s
(
s
)
\frac{\Delta I }{\Delta s}=\tau_s(s)I_s(s)
ΔsΔI=τs(s)Is(s)
体渲染方程:
我们把以上四个过程都综合到一个公式中:
Δ
I
Δ
s
=
τ
a
(
s
)
I
e
(
s
)
+
τ
s
(
s
)
I
s
(
s
)
−
τ
a
(
s
)
I
(
s
)
−
τ
s
(
s
)
I
(
s
)
(8)
\frac{\Delta I }{\Delta s}=\tau_a(s)I_e(s)+\tau_s(s)I_s(s) -\tau_a(s)I(s)-\tau_s(s)I(s) \tag{8}
ΔsΔI=τa(s)Ie(s)+τs(s)Is(s)−τa(s)I(s)−τs(s)I(s)(8)
其中,吸收和外散射都会削弱光线的辐射强度,并且由于它们都和入射光有关,因此它们共同构成了体渲染中的衰减项 (attenuation item),而粒子发光和内散射都来自独立的光源,因此被称为源项 (source item)。
为了求解方便,定义
τ
t
=
τ
a
+
τ
s
\tau_t=\tau_a+\tau_s
τt=τa+τs,式(8)化简为:
d
I
d
s
=
τ
a
(
s
)
I
e
(
s
)
+
τ
s
(
s
)
I
s
(
s
)
−
τ
t
(
s
)
I
(
s
)
\frac{d I }{d s}=\tau_a(s)I_e(s)+\tau_s(s)I_s(s) -\tau_t(s)I(s)
dsdI=τa(s)Ie(s)+τs(s)Is(s)−τt(s)I(s)
求解对应的微分方程,得到:
I
(
s
)
=
∫
0
s
e
x
p
(
−
∫
0
t
τ
t
(
u
)
d
u
[
τ
a
(
t
)
I
e
(
t
)
+
τ
s
(
t
)
I
s
(
t
)
]
d
t
)
+
I
0
e
x
p
(
−
∫
0
δ
τ
(
t
)
d
t
)
(10)
I(s)=\int_0^sexp(-\int^t_0\tau_t(u)du[\tau_a(t)I_e(t)+\tau_s(t)I_s(t)]dt)+I_0exp(-\int^{\delta}_0\tau(t)dt) \tag{10}
I(s)=∫0sexp(−∫0tτt(u)du[τa(t)Ie(t)+τs(t)Is(t)]dt)+I0exp(−∫0δτ(t)dt)(10)
式10就是体渲染的渲染方程,其中
I
0
I_0
I0就是入射光最开始的强度,在N二RF中把它当作式背景光。这个公式告诉我们,出射光的强度只要是由入射光(背景光)和源项(粒子发光以及内散射)等构成,这两者在传递过程中都伴随着相同的衰减
τ
(
t
)
\tau(t)
τ(t)。同时由于从
I
0
I_0
I0到
I
s
I_s
Is的整个光路上,处处都可能存在粒子发光和内散射现象,因此源项是需要从 0 开始相加一直到 s 的,对应到公式中就是积分
∫
0
s
\int^s_0
∫0s。
有了这个公式,我们就可以建模出每一条光线在传播介质中,辐射强度的变化,并最终渲染出图像 (当然前提要知道介质中每一点的 τ ( t ) \tau(t) τ(t) 、 I e I_e Ie等信息)。
注意,虽然我们在讨论这个模型的时候一直是用辐射强度这个概念,但直接把 I ( s ) I(s) I(s)替换成颜色也是可以的。
现在,我们做一个大胆的假设,假设
τ
(
t
)
\tau(t)
τ(t) 、
τ
(
a
)
\tau(a)
τ(a)、
τ
(
s
)
\tau(s)
τ(s) 这些都相等,统一用
σ
\sigma
σ表示,同时令
C
=
I
e
+
I
s
C=I_e+I_s
C=Ie+Is,那么公式 (10) 可以进一步简化成:
I
(
s
)
=
∫
0
s
e
x
p
(
−
∫
0
t
σ
(
u
)
d
u
)
σ
(
t
)
C
(
t
)
d
t
+
I
0
e
x
p
(
−
∫
0
s
σ
(
t
)
d
t
)
=
∫
0
s
T
(
t
)
σ
(
t
)
C
(
t
)
d
t
+
T
(
s
)
I
0
(11)
I(s)=\int^s_0exp(-\int^t_0\sigma(u)du)\sigma(t)C(t)dt+I_0exp(-\int^s_0\sigma(t)dt)\\ =\int^s_0T(t)\sigma(t)C(t)dt+T(s)I_0 \tag{11}
I(s)=∫0sexp(−∫0tσ(u)du)σ(t)C(t)dt+I0exp(−∫0sσ(t)dt)=∫0sT(t)σ(t)C(t)dt+T(s)I0(11)
其中
T
(
s
)
=
e
x
p
(
−
∫
0
s
σ
(
t
)
d
t
)
T(s)=exp(-\int^s_0\sigma(t)dt)
T(s)=exp(−∫0sσ(t)dt)。
上式和NeRF论文中给出的体渲染公式已经很像了,只是多了背景光这一项,如下图NeRF论文中的公式项。
离散化体渲染
公式 (11) 在计算机中是无法表达的,需要进一步离散化。
我们将整个光路 [ 0 , s ] [0,s] [0,s]划分为 N 个相等间距的区间: [ t n , t n + 1 ] [t_n,t_{n+1}] [tn,tn+1]。那么,只要能算出每个 [ t n , t n + 1 ] [t_n,t_{n+1}] [tn,tn+1]区间内的辐射强度 I ( t n − > t n + 1 ) I(t_n->t_{n+1}) I(tn−>tn+1),最后把 N N N个区间的辐射加起来,就可以得到最终的光线强度了。 N N N 越大,则越接近理论数值 (公式 (11))。
那如何计算 I ( t n − > t n + 1 ) I(t_n->t_{n+1}) I(tn−>tn+1) 呢?
为了方便运算,我们假设在每个区间内,
σ
(
t
)
\sigma(t)
σ(t)处处等于
σ
n
\sigma_n
σn,
C
(
t
)
C(t)
C(t) 处处等于
C
t
C_t
Ct。那么可以得到:
I
(
t
n
−
t
n
+
1
)
=
∫
t
n
t
n
+
1
T
(
t
)
σ
n
C
n
d
t
=
σ
n
C
n
∫
t
n
t
n
+
1
T
(
t
)
d
t
(12)
I(t_n-t_{n+1})=\int^{t_{n+1}}_{t_n}T(t)\sigma_nC_ndt=\sigma_nC_n\int^{t_{n+1}}_{t_n}T(t)dt \tag{12}
I(tn−tn+1)=∫tntn+1T(t)σnCndt=σnCn∫tntn+1T(t)dt(12)
注意
T
(
t
)
=
e
x
p
(
−
∫
0
t
σ
(
u
)
d
u
)
T(t)=exp(-\int^t_0\sigma(u)du)
T(t)=exp(−∫0tσ(u)du),是需要从光线起点开始积分。不过好在
T
(
t
)
T(t)
T(t)可以拆分成两段的乘积:
T
(
t
)
=
e
x
p
(
−
∫
0
t
σ
(
u
)
d
u
)
=
e
x
p
(
−
[
∫
0
t
n
σ
(
u
)
d
u
+
∫
t
n
t
σ
(
u
)
d
u
]
)
=
e
x
p
(
−
∫
0
t
n
σ
(
u
)
d
u
)
e
x
p
(
−
∫
t
n
t
σ
(
u
)
d
u
)
=
T
(
0
−
>
t
n
)
T
(
t
n
−
>
t
)
(13)
T(t)=exp(-\int^t_0\sigma(u)du) =exp(-[\int^{t_n}_0\sigma(u)du+\int^{t}_{t_n}\sigma(u)du])\\ =exp(-\int^{t_n}_0\sigma(u)du) exp(-\int^{t}_{t_n}\sigma(u)du)\\ =T(0->t_n)T(t_n->t) \tag{13}
T(t)=exp(−∫0tσ(u)du)=exp(−[∫0tnσ(u)du+∫tntσ(u)du])=exp(−∫0tnσ(u)du)exp(−∫tntσ(u)du)=T(0−>tn)T(tn−>t)(13)
公式(12)变成如下:
I
(
t
n
−
>
t
n
+
1
)
=
σ
n
C
n
∫
t
n
t
n
+
1
T
(
0
−
>
t
n
)
T
(
t
n
−
>
t
)
d
t
=
σ
n
C
n
T
(
0
−
>
t
n
)
∫
t
n
t
n
+
1
T
(
t
n
−
>
t
)
d
t
=
σ
n
C
n
T
(
0
−
>
t
n
)
∫
t
n
t
n
+
1
e
x
p
(
−
∫
t
n
t
σ
n
d
u
)
d
t
=
σ
n
C
n
T
(
0
−
>
t
n
)
∫
t
n
t
n
+
1
e
x
p
(
−
σ
n
∣
t
n
t
)
d
t
=
σ
n
C
n
T
(
0
−
>
t
n
)
∫
t
n
t
n
+
1
e
x
p
(
−
σ
n
(
t
−
t
n
)
)
d
t
=
σ
n
C
n
T
(
0
−
>
t
n
)
e
x
p
(
−
σ
n
(
t
−
t
n
)
)
−
σ
n
∣
t
n
t
n
+
1
=
T
(
0
−
>
t
n
)
C
n
(
1
−
e
x
p
(
−
σ
n
(
t
n
+
1
−
t
n
)
)
)
(14)
I(t_n->t_{n+1})=\sigma_nC_n\int^{t_{n+1}}_{t_n}T(0->t_n)T(t_n->t)dt \\=\sigma_nC_nT(0->t_n)\int^{t_{n+1}}_{t_n}T(t_n->t)dt \\=\sigma_nC_nT(0->t_n)\int^{t_{n+1}}_{t_n}exp(-\int^t_{t_n}\sigma_ndu)dt \\=\sigma_nC_nT(0->t_n)\int^{t_{n+1}}_{t_n}exp(-\sigma_n\mid^t_{t_n})dt \\= \sigma_nC_nT(0->t_n)\int^{t_{n+1}}_{t_n}exp(-\sigma_n{(t-t_n)})dt \\= \sigma_nC_nT(0->t_n)\frac{exp(-\sigma_n(t-t_n))}{-\sigma_n}\mid^{t_{n+1}}_{t_{n}} \\=T(0->t_n)C_n(1-exp(-\sigma_n(t_{n+1}-t_n))) \tag{14}
I(tn−>tn+1)=σnCn∫tntn+1T(0−>tn)T(tn−>t)dt=σnCnT(0−>tn)∫tntn+1T(tn−>t)dt=σnCnT(0−>tn)∫tntn+1exp(−∫tntσndu)dt=σnCnT(0−>tn)∫tntn+1exp(−σn∣tnt)dt=σnCnT(0−>tn)∫tntn+1exp(−σn(t−tn))dt=σnCnT(0−>tn)−σnexp(−σn(t−tn))∣tntn+1=T(0−>tn)Cn(1−exp(−σn(tn+1−tn)))(14)
好了,现在我们得到了
I
(
t
n
−
>
t
n
+
1
)
I(t_n->t_{n+1})
I(tn−>tn+1),接下来就是把每一段得累加起来了:
I
(
s
)
=
∫
0
s
T
(
t
)
σ
(
t
)
C
(
t
)
d
t
+
T
(
s
)
I
0
≈
∑
n
=
1
N
I
(
t
n
−
>
t
n
+
1
)
+
T
(
s
)
I
0
=
∑
n
=
1
N
T
(
0
−
>
t
n
)
(
1
−
e
x
p
(
−
σ
n
(
t
n
+
1
−
t
n
)
)
)
C
n
+
T
(
s
)
I
0
(15)
I(s)=\int^s_0T(t)\sigma(t)C(t)dt+T(s)I_0 \approx\sum^N_{n=1}I(t_n->t_{n+1})+T(s)I_0 \\=\sum^N_{n=1}T(0->t_n)(1-exp(-\sigma_n(t_{n+1}-t_n)))C_n+T(s)I_0 \tag{15}
I(s)=∫0sT(t)σ(t)C(t)dt+T(s)I0≈n=1∑NI(tn−>tn+1)+T(s)I0=n=1∑NT(0−>tn)(1−exp(−σn(tn+1−tn)))Cn+T(s)I0(15)
假设
δ
n
=
t
n
+
1
−
t
n
\delta_n=t_{n+1}-t_n
δn=tn+1−tn,那么
δ
n
\delta_n
δn表示的就是每个小区间的长度,令
T
n
=
T
(
0
−
>
t
n
)
T_n=T(0->t_n)
Tn=T(0−>tn),则
T
n
T_n
Tn也可以离散化为:
T
n
=
e
x
p
(
−
∫
0
t
n
σ
(
u
)
d
u
)
≈
e
x
p
(
∑
k
=
1
n
−
1
−
σ
k
δ
k
)
(16)
T_n=exp(-\int^{t_n}_{0}\sigma(u)du) \approx exp(\sum^{n-1}_{k=1}-\sigma_k\delta_k) \tag{16}
Tn=exp(−∫0tnσ(u)du)≈exp(k=1∑n−1−σkδk)(16)
将公式(16)带入(15)之后,可以得到最终计算机使用的体渲染公式 :
I
(
s
)
=
∑
n
=
1
N
T
n
(
1
−
e
x
p
(
−
σ
n
δ
n
)
)
C
n
+
T
(
s
)
I
0
(17)
I(s)=\sum^N_{n=1}T_n(1-exp(-\sigma_n\delta_n))C_n+T(s)I_0 \tag{17}
I(s)=n=1∑NTn(1−exp(−σnδn))Cn+T(s)I0(17)
上述公式就是NeRF论文中的公式出处,只不过公式(17)加了背景光这一项 。