栅格化渲染源码解析-neural_renderer源码(二)光照部分
目录
源码参见如下链接,这里详细对源码进行解析。其实严格而言,光照部分是在栅格化操作之外的流程,因为这里涉及到光源对于三维物体的相互作用,作用结果是作用在物体的贴图上,严格而言并没有到栅格化的步骤。但是此步骤依然是比较重要的步骤。
另外有一点需要注意,就是Neural_renderer的光照只实现了平行光和常数化光场,并且只实现了漫反射的BRDF。如果需要其他光源形式或者漫反射形式的话,此源码只能起到参考作用,其他作用不大。
GitHub - FuxiCV/pt_mesh_renderer: A PyTorch implementation for the "Mesh Renderer"
https://arxiv.org/abs/2008.07154https://openaccess.thecvf.com/content_cvpr_2018/html/Genova_Unsupervised_Training_for_CVPR_2018_paper.html
一、变量形式
1.1 light
light的数量与面片一致,
# create light
light = xp.zeros((bs, nf, 3), 'float32') # [batch_size, num_faces, RGB]
最初的light的张量维度为 [batch_size, num_faces, RGB],即batch_size, 面片数量,作用到该面片上的光的颜色。通过这里,我们可以发现,light变量的作用是,模拟光源和每个面片的作用,即每个面片上光源的颜色。与最后的代码中我们也可以看到
# apply
light = cf.broadcast_to(light[:, :, None, None, None, :], textures.shape)
textures = textures * light
这里,光源转换为面片一致的维度之后,直接与面片进行了乘法。
所以光源这个变量的作用就是计算每个面片上需要的光,然后直接与面片贴图相乘即可。
1.3 textures变量
此变量主要用于存储物体不同面片上的采样贴图。维度是[batch_size, num_faces, texture_size, texture_size, texture_size, RGB]。相当于,对于物体上的每个面片,是一个边长texture_size的三维张量。为什么每个面片上贴图是二维的,但用于表示此贴图的张量是三维的呢?主要是因为每个面片有三个顶点,贴图就是根据到三个顶点之间有三个距离,那到每个顶点对应于原始二维贴图上的不同的权重,根据距离三个顶点的距离权重的不同,生成了一个三维的张量。其中,texture_size越大,则渲染时贴图的分辨率越高。
此变量在后续栅格化过程中,用于生成图片时候的采样时会用到。在光照的过程中,此变量的作用是与light进行相乘。从而模拟光照的作用。
1.4 faces变量
这个变量用于记录物体的所有三维信息。对于多边形的物体,每个面片有三个顶点,即v1,v2,v3。每个顶点在三维空间中有三个坐标,xyz。 faces变量相当于存储了物体的三维信息,从而用于判断光源对于每个面片的入射角度,从而确定物体漫反射的光强。
二、对于light的处理
2.1 ambient_light常数化光场
对于常数化光场,即各个方向的光照均等,因此直接对光源进行增扩即可,即物体每个面片得到的光照均为color_ambient
# arguments
if isinstance(color_ambient, tuple) or isinstance(color_ambient, list):
color_ambient = xp.array(color_ambient, 'float32')
if color_ambient.ndim == 1:
color_ambient = cf.broadcast_to(color_ambient[None, :], (bs, 3))
2.2 directional_light
对于平行光,即有方向的光场,就需要考虑到光场和面片的相互作用了。面片是具有法向和位置的,光场是平行的,因此就是向量的点乘,不是像常数化光场一样的张量乘。
这里的处理是,直接根据物体的法向算出与光线的夹角,这样方向光场生成变量light的时候,直接根据夹角对光强度进行采样。light变量通过此操作,直接实现了光照到物体上的强度。
if isinstance(color_directional, tuple) or isinstance(color_directional, list):
color_directional = xp.array(color_directional, 'float32')
if color_ambient.ndim == 1:
color_ambient = cf.broadcast_to(color_ambient[None, :], (bs, 3))
if color_directional.ndim == 1:
color_directional = cf.broadcast_to(color_directional[None, :], (bs, 3))
if direction.ndim == 1:
direction = cf.broadcast_to(direction[None, :], (bs, 3))
面片法向计算:
# directional light
if intensity_directional != 0:
faces = faces.reshape((bs * nf, 3, 3))
v10 = faces[:, 0] - faces[:, 1]
v12 = faces[:, 2] - faces[:, 1]
normals = cf.normalize(neural_renderer.cross(v10, v12))
normals = normals.reshape((bs, nf, 3))
计算法向和光照方向的夹角:
注意,这里relu的作用就是让光照的背面不被渲染
if direction.ndim == 2:
direction = cf.broadcast_to(direction[:, None, :], normals.shape)
cos = cf.relu(cf.sum(normals * direction, axis=2))
根据夹角计算相乘
light = (
light + intensity_directional * cfmath.mul(*cf.broadcast(color_directional[:, None, :], cos[:, :, None])))
如果对与整个光照流程比较熟悉的话,此源码读懂较为简单。同时因为源码没有考虑到面片之间的相互遮挡,只考虑到光照的直接作用,因此是较为简单的。如果涉及到面片之间的相互遮挡和影子,可能还需要用到z-buffer,具体流程可以参考openGL源码,博主没有研究过,这里暂不详细讨论了。
上一篇: Android动画学习之帧动画
下一篇: Lua学习笔记之数据结构