目录
一、栅格化渲染
目前三维渲染中,较多渲染技术采用栅格化的方法。也有光追渲染的技术,光追是另一套渲染pipeline,这里暂不讨论光追,只讨论栅格化。
栅格化的定义:将三维场景中的物体进行着色投影到成像平面,形成二维图形的过程。其详细定义和流程可以参考其他文章,本文暂不重复描述。
基于代码选取,栅格化渲染的代码较多,例如基于openGL的渲染管线,本文选取的是neural_renderer,此代码主要的创新点是如何在渲染中回传梯度,但是其栅格化部分的代码写的具有不错的可读性。我们暂不讨论代码中关于梯度的问题,只是详细解析一下neural_renderer是如何进行栅格化渲染的流程的。
二、neural_renderer
有三个版本,一个是torch+cuda版本,另一个是chianer+cupy版本。还有一个tensorflow版本,几个版本之间可以相互参考。
GitHub - FuxiCV/pt_mesh_renderer: A PyTorch implementation for the "Mesh Renderer"
https://arxiv.org/abs/2008.07154
本篇论文的重要贡献在于,将栅格化过程作为一个可以向回传递梯度的过程,从而产生优化,详见论文项目到贡献点和论文原文。这里不详细讨论。本文只详细讨论文章的源码中与栅格化渲染相关的部分。
源码解析中,主要以chainer版本源码做为参考,同时参考另两个版本。chainer是一个类似于神经网络库,在栅格化代码中的作用主要是提供张量乘法和进行GPU任务的并行。例如,代码中并行调用GPU的方法是chainer.cuda.elementwise(...),此步骤中的代码也是光栅化渲染的核心代码。
三、总体流程
此流程可能与openGL的渲染pipeline有不同地方。
- 加载物体:将物体从.obj文件之中加载出来。
- 生成面片张量:用于将面片和顶点结合,张量形式易于处理。(因为.obj文件之中的顶点和面片是独立分开的,而这里转换张量更易于GPU进行并行运算。)
- 对于贴图光照:此步骤只对贴图进行了处理。注意,这里的光照只有平行光和常数化光场(可以理解为光照是从各个方向均匀的照射过来)
- 顶点投影:用对齐的视角进行投影
- 栅格化采样:根据投影结果进行栅格化采样。
3.1加载物体
加载物体,物体被加载为几个张量,vertices, faces, textures. 分别是顶点,面片,贴图。
vertices顶点即物体所有多面体的顶点。维度是 [num_vertices, xyz] 每个顶点的xyz三维坐标
faces面片是顶点之间的组合关系。[num_faces, v123] 面片数量和每个面片的三个顶点。v1,v2,v3这个是一个索引,即该面片在整个顶点的之中的第几个。每三个顶点组合在一起,形成了一个面片。
texture贴图:xyz贴图与面片一一对应,即根据贴图图像生成的针对每个面片的RGB值。该张量尺寸是
[num_faces, texture_size, texture_size, texture_size, RGB]
这里的texture_size不是整张贴图的尺寸,而是每个三角面片上需要贴图的大小。至于为什么三角面片是二维的,而这里的texture_size在这个张量中有三个维度,这点可能需要从load_obj的源码入手。源码中,每个面片有三个顶点,后面采样时候需要用到每两个顶点的权重关系,贴图生成的时候是一个三维的张量。具体看不懂没关系,后面会详细解释一下这个张量的作用。
3.2 渲染总体流程
根据顶点生成面片张量。相当于将顶点和面片统一到一个张量之中,方便后续并行化处理。
.obj文件之中,顶点和面片是独立分开的,顶点是顶点,面片是面片,
顶点和面片转为张量,此张量是 [num_faces, v012, xyz],这个张量蕴含了物体所有的顶点信息。相当于将物体的空间信息存储在张量之中。每个面片,每个面片的三个顶点,每个顶点的三维坐标。
即所有面片的三个顶点的三维坐标。这个张量相当于将.obj模型之中的顶点给统一了起来。
3.3 光照
目前,此代码只实现了光源和面片之间的相互作用,且只实现了漫反射的光照。光源形式只包括常数化光场(即各个方向都有光照过来),和平行光(光从同一个方向照射过来)。
此光照只作用于面片,根据面片的法向朝向与光照相互作用。
3.4 顶点投影
根据摄像机视角和内外参数,将物体的顶点投影到成像平面。即空间坐标到UV坐标的转换。
3.5 栅格化
原理部分可以参考这篇文章: 猴子也能看懂的渲染管线(Render Pipeline) - 知乎 (zhihu.com)
z-buffer的概念:深度缓存z-buffer - 知乎 (zhihu.com)
栅格化的过程是代码的核心。这里涉及到一个z-buffer的概念,即物体经过投影之后,
torch版本的neural_renderer是将代码写成.cu和.cpp文档,通过编译之后用python调用。
chainer版本的nerual_renderer源码是通过chainer.cuda.elementwise(...)进行处理。归结起来,chainer版本的源码更加易于修改,因为只需要修改函数之中的代码即可重新运行,不需要像torch版本的源码一样,需要编译。
栅格化过程涉及到三个部分,这里涉及到cuda并行:
for_each_face:计算该face在当前视角下的投影,计算该face的depth
for_each_pixel: 计算该pixel下所有涉及面片的深度,找到离投影平面最近的面片的索引(相当于z-buffer的作用)
texture_sampling: 根据贴图和前两步的结果进行采样,形成采样图像。
以上内容只是粗略介绍了一下栅格化的框架,以及代码结构,详细源码会在后面进行解析。