NeRF中的体渲染理解
先祭上NeRF中的体渲染公式
C(tN+1)=∑n=1NTn⋅(1−exp(−σnδn))⋅cn, where Tn=exp(∑k=1n−1−σkδk)
\mathbf{C}(t_{N+1})=\sum_{n=1}^N\mathcal{T}_n\cdot (1-\text{exp}(-\sigma_n\delta_n))\cdot \mathbf{c}_n\text{, where }\mathcal{T}_n=\text{exp}(\sum_{k=1}^{n-1}-\sigma_k\delta_k)
C(tN+1)=n=1∑NTn⋅(1−exp(−σnδn))⋅cn, where Tn=exp(k=1∑n−1−σkδk)
其中tNt_NtN为射线参数方程中的参数ttt,(R(t)=o+tdR(t)=o+tdR(t)=o+td)且t1=0t_1=0t1=0,T\mathcal{T}T为透射率,δn=tn+1−tn\delta_n=t_{n+1}-t_nδn=tn+1−tn为第n+1个采样点与第n个采样点的间距,cn\mathbf{c}_ncn为该段体中的颜色常量。以上公式是离散同质的,认为在每段中颜色和体密度不变。
通过透射率和不透明度α\alphaα的关系转换αn=1−T(n→n+1)\alpha_n=1-T(n\to n+1)αn=1−T(n→n+1)可以将上述公式转换为:
C(tN+1)=∑n=1NTn⋅αn⋅cn, where Tn=∏n=1N−1(1−αn)
\mathbf{C}(t_{N+1})=\sum_{n=1}^N\mathcal{T}_n\cdot \alpha_n\cdot \mathbf{c}_n\text{, where }\mathcal{T}_n=\prod_{n=1}^{N-1}(1-\alpha_n)
C(tN+1)=n=1∑NTn⋅αn⋅cn, where Tn=n=1∏N−1(1−αn)
不懂体渲染的人第一眼绝对是看不懂的,下面结合一篇专门针对NeRF中体渲染的论文细说。
α\alphaα混合
在光栅化中的透明材质渲染中用到了α\alphaα混合,在OpenGL中叫做blending,alpha test。是在渲染透明贴图中使用的技术,具体如下:
现有两个纹理都是RGBA图像,其中绿色的纹理alpha值为0.6,红色完全不透明。现在的情况是绿色纹理有一部分叠加到了红色纹理上,该怎么渲染?当然是这样渲染:
没有叠加的部分还是原来纹理中的颜色,不透明度也是原来的值。叠加部分的颜色加权相加,但是叠加部分的不透明度需要另算。
使用GL中形式化的描述如下:
红色纹理作为底片,在GL中已经是保存在某个Color Buffer中,这里称它为Destination Color。而绿色纹理作为叠加在红色底片上的纹理这里称为Source Color,现在要求的是最终的Result Color。GL以这样的方式混合:
CResulat=CSource∗FSource+CDestination∗FDestination
C_{Resulat}={\color{green}C_{Source}*F_{Source}}+{\color{red}C_{Destination}*F_{Destination}}
CResulat=CSource∗FSource+CDestination∗FDestination
C当然是颜色,F为alpha因子,需要程序员手动设置。这里用这样的策略设置,上方的绿色alpha值为0.6,那红色就给1-0.6=0.4,这样做也有一定的道理,而这也是体渲染的核心思想:
不透明度是一个[0,1][0,1][0,1]之间的值,说明的是多少光没有穿透这个纹理,值在[0,1][0,1][0,1]之间很容易让人联想到概率,体渲染在此引入随机性,后面细锁。
叠加部分的颜色很好算,如上式用不透明度加权求和:
CResult=0.6∗Vec3(0.0,1.0,0.0)+(1−0.6)∗Vec3(1.0,0.0,0.0)=(0.6,0.4,0)
C_{Result}=0.6*\text{Vec3}(0.0,1.0,0.0)+(1-0.6)*\text{Vec3}(1.0,0.0,0.0)=(0.6,0.4,0)
CResult=0.6∗Vec3(0.0,1.0,0.0)+(1−0.6)∗Vec3(1.0,0.0,0.0)=(0.6,0.4,0)
而透明度怎么算?上面说不透明度是有多少光不能穿过该纹理,那么光经过两个不透明纹理有多少光不能透射就是混合后的不透明度:
αCompositing=1−(1−0.6)∗(1−0.4)=1−0.4∗0.6=0.76
\alpha_{Compositing}=1-(1-0.6)*(1-0.4)=1-0.4*0.6=0.76
αCompositing=1−(1−0.6)∗(1−0.4)=1−0.4∗0.6=0.76
就是这么简单。而这些都是发生在离散域中的,纹理就是一张没有厚度的图像(或者说表面),体渲染是在体数据中进行的,推导在连续域中推导,但是最终的计算还是要回到离散。
计算时关闭Depth Test,顺序不能反。
体渲染
这是我的理解,因为时间有限,忙着开题,仅从相关的资料中了解了个大概,有问题请指出。
背景:光栅化和光追对于一些体状目标渲染质量不好,如云,火焰等在物理模拟中多用粒子系统,最后的渲染还是跟光栅化和光追的渲染方式相同,用纹理,这样的话最终的效果就被纹理限制死了,达不到逼真的效果。体渲染用更好的物理模型建模这种渲染,效果更好。
体渲染中认为在空间中有许多粒子,不考虑光照,光照射在该空间中,这些粒子会吸收光能,并辐射出光能,不考虑粒子对光的散射,在连续3D空间中通过体密度来计算视相关光能的累计从而得到最后的RGB值。
- 首先,不考虑光照,其实跟Blinn-Phong中ambient light的思想很像:不管间接光照。当然这里只是体现在不计算光源相关的东西。这应该也是NeRF对光源扰动不鲁棒的原因。
- 齐次,absorption-emission模型是在辐射度量学中的一个思想:ray打在物体表面,然后弹射的这个过程可以理解为物体吸收光子,其中一些能量转换为内能,还有一部分又以光子的形式辐射出去。
- 不考虑光的散射,吸收再被发射出的光子只会沿着它本来的方向传播。
体数据
之前的体渲染使用体数据进行渲染,典型的体数据有CT,体素数据,在某个维度以固定间距堆叠的RGBA图像,或者RGB-A转移方程或者3D纹理。体渲染通过发射射线穿过这些体数据,然后计算射线上采样点的累计光能得到RGB,这个过程叫做着色与混合,具体的计算公式推导如下。
体密度与透射率
在3D空间中定义体密度σ(x)\sigma(\mathbf{x})σ(x),其中x\mathbf{x}x为空间中的一个位置,体密度的含义为一条射线在该位置碰到粒子的可能性微元,可以这么理解,一团云中有水汽,但是云是动态的,在一个穿过云层的连续一维射线上,每个位置上都有可能有水汽也有可能没有,体密度字面理解就是这个体数据中粒子在所有地方的密度。
射线可以用参数方程R(t)=o+tdR(t)=o+tdR(t)=o+td表示,体密度也可以用ttt来参数化σ(t)\sigma(t)σ(t),这样的话自变量是标量ttt方便计算。透射率T\mathcal{T}T和体密度密切相关,含义为在区间[0,t)[0,t)[0,t)上射线都没有碰到粒子的可能性。可以看出这体密度和透射率都是随机变量。
透射率推导
考虑T(t+dt)\mathcal{T}(t+dt)T(t+dt),含义为在时间步t+dtt+dtt+dt射线没有碰到粒子的概率,显然等于时间步ttt射线没有碰到粒子的概率乘以1减去σ(t)\sigma(t)σ(t)乘以dtdtdt
T(t)⋅(1−σ(t)dt)
T(t)\cdot(1-\sigma(t)dt)
T(t)⋅(1−σ(t)dt)
其中1减去σ(t)\sigma(t)σ(t)乘以dtdtdt表示在这一个时=间步微分中射线碰到粒子的概率(体密度定义)。于是有定理1:
T(t+dt)=T(t)⋅(1−σ(t)dt)T(t+dt)−T(t)dt=−T(t)⋅σ(t)T′(t)T(t)=−σ(t)∫abT′(t)T(t)dt=−∫abσ(t)dtln(T(t))∣ab=−∫abσ(t)dtT(b)−T(a)=exp(−∫abσ(t)dt)⟹T(b→a)=exp(−∫abσ(t)dt)
\mathcal{T}(t+dt)=\mathcal{T}(t)\cdot(1-\sigma(t)dt)\\
\dfrac{\mathcal{T}(t+dt)-\mathcal{T}(t)}{dt}=-\mathcal{T}(t)\cdot\sigma(t)\\
\dfrac{\mathcal{T}'(t)}{\mathcal{T}(t)}=-\sigma(t)\\
\int_a^b\dfrac{\mathcal{T}'(t)}{\mathcal{T}(t)}dt=-\int_a^b{\sigma(t)}dt\\
ln(\mathcal{T}(t))\big\vert_a^b=-\int_a^b{\sigma(t)}dt\\
\mathcal{T}(b)-\mathcal{T}(a)=\text{exp}(-\int_a^b{\sigma(t)}dt) \Longrightarrow
\mathcal{T}(b\to a)=\text{exp}(-\int_a^b{\sigma(t)}dt)
T(t+dt)=T(t)⋅(1−σ(t)dt)dtT(t+dt)−T(t)=−T(t)⋅σ(t)T(t)T′(t)=−σ(t)∫abT(t)T′(t)dt=−∫abσ(t)dtln(T(t))ab=−∫abσ(t)dtT(b)−T(a)=exp(−∫abσ(t)dt)⟹T(b→a)=exp(−∫abσ(t)dt)
定理1是透射率T\mathcal{T}T和体密度σ\sigmaσ的关系,透射率含义为从a到b区间上射线不碰到粒子的概率。之后透射率T(t)\mathcal{T}(t)T(t)为T(0→t)\mathcal{T}(0\to t)T(0→t)的简写。
这个幂函数也符合直觉,一条穿过体数据的射线,时间ttt越大,碰到粒子的可能性就越大,透射率就越小:
概率论
透射率和体密度都是有随机性的,如上所说透射率T(t)\mathcal{T}(t)T(t)为光线在其参数在区间[0,t)[0,t)[0,t)上传播时没有碰到粒子的概率,那么
1−T(t)=1−exp(−∫0tσ(t)dt)
1-\mathcal{T}(t)=1-\text{exp}(-\int_0^t\sigma(t)dt)
1−T(t)=1−exp(−∫0tσ(t)dt)
就是在区间[0,t)[0,t)[0,t)上射线碰到粒子(alpha)的CDF。而在ttt时刻射线碰到粒子而停止传播的概率为T⋅σ(t)\mathcal{T}\cdot\sigma(t)T⋅σ(t),这是对应的PDF。
体渲染推导
因为射线在某个时刻停止传播的PDF为T(t)σ(t)\mathcal{T}(t)\sigma(t)T(t)σ(t),所以可以用这项计算颜色的期望:假设射线传播到D时刻停止传播,但是有一个背景色(其实跟之前GL的Destination Color一样,就算光被阻挡也不是全部被阻挡,还是能看到背景色的)
C=∫0DT(t)⋅σ(t)⋅c(t)dt+T(D)⋅cbg
\mathbf{C}=\int_0^D\mathcal{T}(t)\cdot \sigma(t)\cdot\mathbf{c}(t)dt+\mathcal{T}(D)\cdot \mathbf{c}_{bg}
C=∫0DT(t)⋅σ(t)⋅c(t)dt+T(D)⋅cbg
后面忽略背景色。
Lemma 1
计算离散颜色期望。
如果有RGBA Transfer Function的话,就可以通过查询该函数得到任意一个点的RGBA值,可以使用连续求法求,但是大多数情况是做不到的,比如体素。因此需要一个中离散求法,对连续函数采样得到离散值,然后将该离散的阶梯函数代替原连续函数,这样在每个离散区间内可以认为体密度和颜色都是同质的(homogeneous),Lemma 1就是对同质区间求颜色期望。
假设射线在区间[a,b][a,b][a,b]传播,该区间上的体密度和颜色都是常量σa,ca\sigma_a, c_aσa,ca,现在求该区间颜色期望:
C(a→b)=∫abT(a→t)⋅σ(t)⋅c(t)dt=σa⋅ca∫abT(a→t)dt=σa⋅ca∫abexp(−∫atσ(u)du)dt=σa⋅ca∫abexp(−σau∣at)dt=σa⋅ca⋅exp(−σa(t−a))−σa∣ab=ca⋅(1−exp(−σa(b−a))
\begin{aligned}
\mathbf{C}(a\to b)&=\int_a^b\mathcal{T}(a\to t)\cdot\sigma(t)\cdot\mathbf{c}(t)dt\\
&=\sigma_a\cdot\mathbf{c}_a\int_a^b\mathcal{T}(a\to t)dt\\
&=\sigma_a\cdot\mathbf{c}_a\int_a^b\text{exp}(-\int_a^t\sigma(u)du)dt\\
&=\sigma_a\cdot\mathbf{c}_a\int_a^b\text{exp}(-\sigma_au\big\vert_a^t)dt\\
&=\sigma_a\cdot\mathbf{c}_a\cdot\dfrac{exp(-\sigma_a(t-a))}{-\sigma_a}\Bigg\vert_a^b\\
&=\mathbf{c}_a\cdot(1-\text{exp}(-\sigma_a(b-a))
\end{aligned}
C(a→b)=∫abT(a→t)⋅σ(t)⋅c(t)dt=σa⋅ca∫abT(a→t)dt=σa⋅ca∫abexp(−∫atσ(u)du)dt=σa⋅ca∫abexp(−σauat)dt=σa⋅ca⋅−σaexp(−σa(t−a))ab=ca⋅(1−exp(−σa(b−a))
后面会用到。
Lemma 2
透射率可乘性
对于a到c区间透射率,如果中间有一个点b,a到c的透射率等于a到b的透射率乘以b到c的透射率:
T(a→c)=exp(−∫acσ(t)dt)=exp(−[∫abσ(t)dt+∫bcσ(t)dt])=exp(−[∫abσ(t)dt])∗exp(−[∫bcσ(t)dt])=T(a→b)∗T(b→c)
\begin{aligned}
\mathcal{T}(a\to c)&=\text{exp}\bigg(-\int_a^c\sigma(t)dt\bigg)\\
&=\text{exp}\bigg(-\bigg[\int_a^b\sigma(t)dt+\int_b^c\sigma(t)dt\bigg]\bigg)\\
&=\text{exp}\bigg(-\bigg[\int_a^b\sigma(t)dt\bigg]\bigg)*\text{exp}\bigg(-\bigg[\int_b^c\sigma(t)dt\bigg]\bigg)\\
&=\mathcal{T}(a\to b)*\mathcal{T}(b\to c)
\end{aligned}
T(a→c)=exp(−∫acσ(t)dt)=exp(−[∫abσ(t)dt+∫bcσ(t)dt])=exp(−[∫abσ(t)dt])∗exp(−[∫bcσ(t)dt])=T(a→b)∗T(b→c)
这是符合直觉的,可以将b想象为一个夹在a,c之间的半透明纹理。
离散推导
离散透射率
对于离散数据,首先形式化语言描述:给定一个区间构成的集合{[tn,tn+1]}n=1N\{[t_n,t_{n+1}]\}_{n=1}^N{[tn,tn+1]}n=1N,共N个区间,t1=0,δn=tn+1−tnt_1=0,\delta_n=t_{n+1}-t_nt1=0,δn=tn+1−tn,σ(t)\sigma(t)σ(t)表示第t个区间内的体密度。每个区间内的提数据同质,即体密度和颜色都相同。则透射率的计算如下:
Tn=T(tn)=T(0→tn)=exp(−∫0tnσ(t)dt)=exp(∑k=1n−1−σkδk)
\mathcal{T}_n=\mathcal{T}(t_n)=\mathcal{T}(0\to t_n)=\text{exp}\bigg(-\int_0^{t_n}\sigma(t)dt\bigg)=\exp\bigg(\sum_{k=1}^{n-1}-\sigma_k\delta_k\bigg)
Tn=T(tn)=T(0→tn)=exp(−∫0tnσ(t)dt)=exp(k=1∑n−1−σkδk)
实际上就是阶梯函数求面积或者说是黎曼和。
离散体渲染
对于离散体数据,可以认为每个区间都是同质的,可以对每段求颜色期望然后累加:
C(tN+1)=∑n=1N∫tntn+1T(t)⋅σn⋅cndt=∑n=1N∫tntn+1T(0→tn)⋅T(tn→t)⋅σn⋅cndtfrom Lemma1=∑n=1NT(0→tn)∫tntn+1T(tn→t)⋅σn⋅cndtT(0→tn) is constant=∑n=1NT(0→tn)⋅(1−exp(−σn(tn+1−tn)))⋅cnfrom Lemma2=∑n=1NT(0→tn)⋅(1−exp(−σnδn))⋅cn
\begin{aligned}
\mathbf{C}(t_{N+1})&=\sum_{n=1}^N\int_{t_n}^{t_{n+1}}\mathcal{T}(t)\cdot\sigma_n\cdot\mathbf{c}_ndt\\
&=\sum_{n=1}^N\int_{t_n}^{t_{n+1}}\mathcal{T}(0\to t_n)\cdot\mathcal{T}(t_n\to t)\cdot\sigma_n\cdot\mathbf{c}_ndt&\text{from Lemma1}\\
&=\sum_{n=1}^N\mathcal{T}(0\to t_n)\int_{t_n}^{t_{n+1}}\mathcal{T}(t_n\to t)\cdot\sigma_n\cdot\mathbf{c}_ndt&\mathcal{T}(0\to t_n)\text{ is constant}\\
&=\sum_{n=1}^N\mathcal{T}(0\to t_n)\cdot (1-\text{exp}(-\sigma_n(t_{n+1}-t_n)))\cdot\mathbf{c}_n&\text{from Lemma2}\\
&=\sum_{n=1}^N\mathcal{T}(0\to t_n)\cdot (1-\text{exp}(-\sigma_n\delta_n))\cdot\mathbf{c}_n
\end{aligned}
C(tN+1)=n=1∑N∫tntn+1T(t)⋅σn⋅cndt=n=1∑N∫tntn+1T(0→tn)⋅T(tn→t)⋅σn⋅cndt=n=1∑NT(0→tn)∫tntn+1T(tn→t)⋅σn⋅cndt=n=1∑NT(0→tn)⋅(1−exp(−σn(tn+1−tn)))⋅cn=n=1∑NT(0→tn)⋅(1−exp(−σnδn))⋅cnfrom Lemma1T(0→tn) is constantfrom Lemma2
上面两个式子经过一个代换就是NeRF中的式子。
考虑不透明度α\alphaα,其实表示的是光经过一个半透明纹理时有多少光被阻挡了(只不过光栅化中纹理没有厚度),也就是不透光率1−T(a→b)1-\mathcal{T(a\to b)}1−T(a→b),在离散情况下为1−exp(−σkδk)1-\text{exp}(-\sigma_k\delta_k)1−exp(−σkδk),用opacity代替得到最终的表达式:
C(tN+1)=∑n=1NTn⋅αn⋅cn, where Tn=∏n=1N−1(1−αn)
\mathbf{C}(t_{N+1})=\sum_{n=1}^N{\color{red}\mathcal{T}_n}\cdot{\color{green}\alpha_n}\cdot {\color{blue}\mathbf{c}_n}\text{, where }\mathcal{T}_n=\prod_{n=1}^{N-1}(1-\alpha_n)
C(tN+1)=n=1∑NTn⋅αn⋅cn, where Tn=n=1∏N−1(1−αn)
其中透射率:
Tn=exp(∑k=1n−1−σkδk)=∏k=1n−1exp(−σkδk)=∏k=1n−1(1−αk)
\mathcal{T}_n=\exp\bigg(\sum_{k=1}^{n-1}-\sigma_k\delta_k\bigg)=\prod_{k=1}^{n-1}\text{exp}(-\sigma_k\delta_k)=\prod_{k=1}^{n-1}(1-\alpha_k)
Tn=exp(k=1∑n−1−σkδk)=k=1∏n−1exp(−σkδk)=k=1∏n−1(1−αk)
公式中的透射率来自出发点到采样点前一个坐标点tnt_ntn的透射率,αn\alpha_nαn为该坐标点到采样点之间的不透明度,cn\mathbf{c}_ncn为区间[tn,tn+1][t_n,t_{n+1}][tn,tn+1]发光粒子的颜色