Nerf原始论文

什么是Nerf

  • nerf是一种通过隐式表达做新视角合成任务 (novel view synthesis task) 的工具, 隐式表达指的是在渲染过程中不对目标物体或者场景进行显示的建模
  • 其输入是某个视角下发射的视角方向d=(θ, Φ), 以及对应的坐标x,y,z, 通过神经辐射场\(F\theta\), 得到体密度和颜色. 最后通过体渲染 (volumetric rendering) 渲染得到最终的图像.

Nerf流程

总的来说,NeRF的流程分为3步:

  1. 使用raysampler生成光线rays (包含输入的光线方向, 起点, 位置).

  2. 对生成的sampled rays, 调用volumetric函数(即NeuralRadianceField-nerf/nerf/implicit_function.py), 得到rays_densities σrays_features c.

  3. 最后, 沿着光线积分颜色, 得到最终图像

流程具象化

  • 我理解为在需要渲染的图片上每个像素点按特定方向发出一条光线, 然后每条光线根据不同的t(可以理解为深度)采样得到一些点, 如蓝色球. 最后输入神经辐射场\(F\theta\) 的内容就是经过位置编码\(\gamma\)的蓝色球的坐标x,y,z以及视角方向d.
  • 经过神经辐射场\(F_{\theta}\)的处理,我们得到了每一个小球的颜色c和密度σ(颜色已经不一样了~),那么根据体渲染(Volume Rendering)的机制,就可以进行渲染了。

附录



何为体渲染

定义

  • 依据三维体数据,将所有体细节同时展现在二维图片上的技术,称之为体绘制技术。利用体绘制技术,可以在一幅图像中显示多种物质的综合分布情况,并且可以通过不透明度的控制,反应等值面的情况。(类似安检时的检测图, 光线从某个方向出发, 穿过整个物体.)
  • 体渲染的可视化效果如下

体数据

  • 只要是包含了体细节的数据,都可以称之为体数据。举个例子,有一堆混凝土,其中包含了碳物质( C )若干,水分子( H20 )若干,还有不明化学成分的胶状物,你用这种混凝土建造了块方砖,如果存在一个三维数组,将方砖 X 、 Y 、 Z 方向上的物质分布表示出来,则该数组可以被称为体数据。

体素( Voxel )

  • 体素,是组成体数据的最小单元,一个体素表示体数据中三维空间某部分的值。体素相当于二维空间中像素的概念, 体素不存在绝对空间位置的概念,只有在体空间中的相对位置,这一点和像素是一样的。

通常我们看到的体数据都会有一个体素分布的描述,即,该数据由 nmt 个体素组成,表示该体数据在 X 、 Y 、 Z 方向上分别有 n 、 m 、 t 个体素。在数据表达上,体素代表三维数组中的一个单元。假设一个体数据在三维空间上 256*256*256 个体素组成,则,如果用三维数组表示,就必须在每一维上分配 256 个空间。

在实际的仪器采样中,会给出体素相邻间隔的数据描述,单位是毫米( mm ),例如 0.412mm 表示该体数据中相邻体素的间隔为 0.412 毫米 。

体纹理( Volume Texture )

  • 三维纹理,即体纹理,是传统 2D 纹理在逻辑上的扩展。二维纹理是一张简单的位图图片,用于为三维模型提供表面点的颜色值;而一个三维纹理,可以被认为由很多张 2D 纹理组成的,用于描述三维空间数据的图片。三维纹理通过三维纹理坐标进行访问

体绘制算法

  • 体绘制中光线投射算法最为常见和通用。光线投射方法是基于图像序列的直接体绘制算法。从图像的每一个像素,沿固定方向(通常是视线方向)发射一条光线,光线穿越整个图像序列,并在这个过程中,对图像序列进行采样获取颜色信息,同时依据光线吸收模型将颜色值进行累加,直至光线穿越整个图像序列,最后得到的颜色值就是渲染图像的颜色。
  • 为什么在上面的定义是穿越 “ 图像序列 ” ,而不是直接使用 “ 体纹理 ” ?原因在于,体数据有多种组织形式,在基于 CPU 的高级语言编程中,有时并不使用体纹理,而是使用图像序列。在基于 GPU 的着色程序中,则必须使用体纹理。这里所说的图像序列,也可以理解为切片数据。

光线投射算法的细节

  • 体纹理并不是空间的模型数据,空间体模型(通常是规则的立方体或圆柱体)和体纹理相互结合才能进行体渲染。
  • 举例而言,我们要在电脑中看到一个纹理贴图效果,那么至少需要一张二维的纹理和一个面片,才能进行纹理贴图操作。这个面片实际上就是纹理的载体。同理,在体绘制中同样需要一个三维模型作为体纹理的载体,体纹理通过纹理坐标(三维)和模型进行对应,然后由视点向模型上的点引射线,该射线穿越模型空间等价于射线穿越了体纹理。
  • 通常使用普通的立方体或者圆柱体作为体绘制的空间模型。注意:体纹理通过纹理坐标和三维模型进行对应

上图展示了体纹理坐标在立方体上的分布,经过测试,这种分布关系是基于 OpenGL 的。在宿主程序中确定立方体 8 个顶点的体纹理坐标,注意是三元向量,然后传入 GPU ,立方体 6 个面内部点的体纹理坐标会在 GPU 上自动插值得到。

根据视点和立方体表面点可以唯一确定一条射线,射线穿越整个立方体等价于穿越体数据,并在穿越过程中对体数据等距采样对每次得到的采样数据按照光透公式进行反复累加

透明度、合成

  • 透明度本质上代表着光穿透物体的能力,光穿透一个物体会导致波长比例的变化,如果穿越多个物体,则这种变化是累加的。所以,透明物体的渲染,本质上是将透明物体的颜色和其后物体的颜色进行混合,这被称为 alpha 混合技术。图形硬件实现 alpha 混合技术,使用 over 操作符。 Alpha 混合技术的公式如下所示:

\[c_0=a_sc_s+(1-a_s)c_d \]

其中\(a_s\)表示物体的透明度, \(c_s\)表示透明物体, \(c_d\)表示目标物体的原本颜色, \(c_o\)则是通过透明物体观察目标物体所得到的颜色值.

如果有多个透明物体,通常需要对物体进行排序,除非所有物体的透明度都是一样的。在图形硬件中实现多个透明物体的绘制是依赖于 Z 缓冲区。在光线投射算法中,射线穿越体纹理的同时也就是透明度的排序过程。所以这里存在一个合成的顺序问题。可以将射线穿越纹理的过程作为采样合成过程,这是从前面到背面进行排序,也可以反过来从背面到前面排序,毫无疑问这两种方式得到的效果是不太一样的。

  • 如果从前面到背面进行采样合成,则合成公式为:

\[C_i^\Delta=(1-A_{i-1}^{\Delta})C_i+C_{i-1}^\Delta \]

\[A_i^\Delta=(1-A_{i-1}^\Delta)A_i+A_{i-1}^\Delta \]

其中, \(C_i\)\(A_i\)分别是在体纹理上采样得到的颜色值和不透明度, 其实也就是体素中蕴含的数据; \(C_i^\Delta\)\(A_i^\Delta\)表示累加的颜色值和不透明度.

  • 注意:很多体纹理其实并没有包含透明度, 所以有时是自己定义一个初始的透明度,然后进行累加.

如果从背景到前面进行采样合成,则公式为:

\[C_i^\Delta=(1-A_i)C_{i+1}^\Delta+C_i \]

\[A_i^\Delta=(1-A_i)A_{i+1}^\Delta+A_i \]

沿射线进行采样

  • 假定光线从 f 点投射到立方体中,并从 l 点投出,在立方体中穿越的距离为 m 。当光线从 f 点投射到立方体中,穿越距离为 n (n<m)时进行采样,则存在公式:

\[t=t_{start}+d*\delta \]

其中 \(t_{start}\)表示立方体表面被投射点的体纹理坐标; d 表示投射方向;\(\delta\) 表示采样间隔,随着 n 的增加而递增;t 为求得的采样纹理坐标。通过求得的采样纹理坐标就可以在体纹理上查询体素数据。直到 n>m ,或者透明度累加超过 1 ,一条射线的采样过程才结束。

下面总结一下:首先需要一个确定了顶点纹理坐标的三维立方体,光线穿越立方体的过程,就是穿越体纹理的过程,在整个穿越过程中,计算采样体纹理坐标,并进行体纹理采样,这个采样过程直到光线投出立方体或者累加的透明度为 1 时结束。

我想这个过程应该不复杂,大家一定要记住:纹理坐标是联系三维模型和体纹理数据之间的桥梁,通过计算光线穿越三维模型,可以计算体纹理在光线穿越方向上的变化,这就是计算采样纹理坐标的方法。

如何判断光线投射出体纹理

  • 光线投射出体纹理,等价于光线投射出立方体。所以如何判断光线投射出体纹理,可以转换为判断光线投射出立方体。

首先计算光线在立方体中入射到出射的行进距离 m ,然后当每次采样体纹理时同时计算光线在立方体中的穿越距离 n ,如果 n>=m ,则说明光线射出立方体。给定光线方向,以及采样的距离间隔,就可以求出光线在立方体中的穿越距离 n 。

在 GPU 中确定点和点之间顺序关系可以使用深度值。
在 GPU 中可以间接反应点和点之间关系的有两个量,一个是纹理坐标,另一个就是深度值。通常在渲染中会进行深度剔除,也就是只显示深度值小的片段。不过也存在另外一个深度剔除,将深度值小的片段剔除,而留下深度值最大的片段(深度值的剔除方法设置,在 OpenGL 和 Direct 中都有现成函数调用)。如果使用后者,则场景中渲染显示的是离视点最远的面片集合。

所以,计算距离 m 的方法如下:

  1. 剔除深度值较大的片段(正常的渲染状态),渲染场景深度图 frontDepth,此时 frontDepth 上的每个像素的颜色值都代表“某个方向上离视点最近的点的距离”;

  2. 剔除深度值较小的片段,渲染场景深度图 backDepth , backDepth 上的每个像素的颜色值都代表“某个方向上离视点最远的点的距离”;

  3. 将两张深度图上的数据进行相减,得到的值就是光线投射距离 m 。

背面渲染

通常,背面的面片(不朝向视点的面片)是不会被渲染出来的,图形学基础比较好的同学应该知道,三个顶点通常按逆时针顺序组成一个三角面,这样做的好处是,背面面片的法向量与视线法向量的点积为负数,可以据此做面片剔除算法(光照模型实现中也常用到),所以只是改变深度值的比较方法还不够,还必须关闭按照逆 / 顺时针进行面片剔除功能,这样才能渲染出背面深度图。
下图是立方体的正面和背面深度图。
image

光线投射算法流程

上图展示了使用光线投射算法进行体绘制的实现流程。

首先要渲染出正向面深度图和背向面深度图,这是为了计算射线穿越的最大距离,做为循环采样控制的结束依据;然后在顶点着色程序中计算顶点位置和射线方向,射线方向由视线方向和点的世界坐标决定,其实射线方向也可以放在片段着色程序中进行计算。然后到了最关键的地方,就是循环纹理采样、合成。

每一次循环都要计算新的采样纹理坐标和采样距离,然后进行颜色合成和透明度累加,如果采样距离超过了最大穿越距离,或者透明度累加到 1 ,则循环结束。将合成得到的颜色值输出即可

下图是使用光线投射算法进行体绘制的效果图

posted @ 2022-08-15 20:47  sdulyq  阅读(912)  评论(0)    收藏  举报