简介
渲染可以分为forward和inverse两种方式,如图所示,当我们有了床的模型,灯光和相机视角后,可以渲染出一张图像。理论上,如果我们获取了该方法的反函数,就可以基于图像获取原始参数。之前一系列小结告诉我们,f方法复杂(light transport & material),计算量很大,无法直接算出这个反函数。于是,退而求其次,我们如果能够获取该函数对应参数x的导数,就可以不断的迭代来拟合,最终得到误差在允许范围内的x的近似值。因此,问题转化为如何求解函数的导数,这就是可微分渲染领域的核心问题。
可以说,这个工作是视觉在更下一层的延伸,传统的视觉受限于计算量,都是针对图像的理解,而现在,我们希望能够更进一步,能够针对这些参数化属性的理解。
微分渲染能够帮助我们更好的解决很多现实的问题。
如图的光照分析,根据建筑物布局,阳光的位置,量化室内的光照亮,方便后续的照明系统设置。
比如3D打印,打印的材料通常都是半透明材质,因此内部会有散射,这样,某一点的颜色不仅仅取决于该点自己的颜色,而是和周围效果的融合,因此会有失真的问题,而微分渲染可以在forward和inverse之间不断微调模拟,获取更佳的打印参数。
光蚀设计,当我们把平行光打到一块玻璃上,希望能够折射出期望的图像
Auto differentiable
为了实现可微分渲染,首先,我们需要能计算一个函数的导数,最直接的方式就是手动推导,这种方式的好处是准确,一旦计算出公式后,计算非常的高效,但如果函数非常复杂,这个推导很麻烦,也容易出错,通常,手动推导不太适合解决实际中的复杂问题,能用体力解决的就不要靠智力。因此,目前主流的是auto diff,大概思路就是引入一个增量ε,计算其导数,问题是如何设置ε的大小会影响其精度,但确实就是纯算,没有太多技术含量。
Auto diff又分为forward和backward两种方式,举个例子,假设有x0,x1,y0,y1,z0这样一个graph:
在forward中,需要
和
,以及
和
,然后利用chain rule得到
。同理计算
,总计需要8次,有重复计算。
而在backward中则可以避免重复计算,我们先计算
和
,然后再计算
和
,以及
和
,没有重复,只有6次。缺点是会占用较多的内存存储中间变量。目前,auto diff通常采用backward方式,先根据函数构建graph,然后求导,这一部分也有很多内容,比如通过元编程等template技术在编译器求导的方式,来降低运行期的计算量。
基于这个例子,我们假设z0是一件工艺品,需要2个y0(假设是钉子)和3个y1(假设是螺丝),其中,一个钉子是0.8元,一个螺丝是0.5元,所以z0价值是:
假设我们只知道z0的成本(target)是3.1,不知道钉子和螺丝的成本,我们给一个初始值,认为它们分别都是1元:
我们设置一个learning rate 是0.1,根据如上的公式更新每个单位的成本,得到如下:
依次继续迭代,我们就可以不断缩小和期望值target之间的差距,也就能够获取较为准确的单位成本,在不陷入local minimum的前提下。
该例子对应的数学推导也不难理解,比如我们构建了一个loss function,如下:
这样,我们针对每一种材料的权重求导:
设置一个合适的learning rate ε,我们可以得到:
如上,就是一个简单的backpropagation的过程,在微分渲染中,我们认为影像类似钉子和螺丝这一层,它们也要依赖最终的x0(原材料)和x1(打工人)等更底层的参数,我们希望能够通过chain rule进一步的了解x层对应的权重,这也是为什么之前说,之前的CV直接使用影像,并没有把渲染的过程包含进去,而微分渲染则提供了这种能力,进而更精确的理解图像。
differentiable rendering
我们先适度装个逼,提出一个问题,空间是连续的吗?这是一个很有意思的问题,普朗克尺度下空间是离散的,而一个光子的能量跟普朗克成正比,从这个角度,radiance在空间中的分布也应该是离散的。
我们看下面这个例子
很显然,被积函数是不连续的(x==p时),但如下求导也成立:
而渲染中,一个像素对应的颜色是该像素区域内所有颜色的均值。所以,尽管一条光路不一定是连续的,但整个积分下则是连续的。The integrand of rendering is discontinuous and not differentiable, but the integral is actually differentiable。这也是可微分渲染的理论基础。
接下来,问题变成了如何对积分求导,如下是对应的数学推导,分为连续区间下和非连续的部分。
下面我们试着在渲染中运用该思路。
如上是Primary visibility的情况,也就是和相机相连的ray对应的渲染效果,类似一面白墙,然后左上部分是绿色的,上图是交界处的一个像素的效果。这里,我们设置一个half-plane函数,作为Heaviside step function (对其求导则是一个Dirac delta function)的参数:
这里,函数α是这个half-plane,α>0是绿色区域,否则为白色区域,假设分界线的起始点为(ax,ay)和(bx,by):
此时,对该像素的积分求导则转化如下,σ是对应edge的长度:
上述公式对应的示意图如下,分为内部可导区域和不可导的边界
其中,对边界的积分计算如下,height为边界两边的差异,比如颜色,radiance,v为边界移动的方向,n为边界的法线方向
如上是针对primary ray的推导,对于second ray的方法类似,区别是primary针对的是2D平面而在second ray,则是3D空间下,如下图:
我们将该方法推广到整个rendering equation:
可见,在可微分渲染中,我们要对两部分求积分,一个是整个光路区域的微分,一个是边界区域的差异,而空间中的边界主要有三部分构成:
我们已经完成了可微分渲染理论上的介绍,基于这个方法,我们可以完成下图中dy/dx的部分,加上已知的dz/dy(目标和当前效果的差 t-z),我们实现了对整体的求导。
Challenges
理论上完善后,在实际应用中,还有两个主要的问题,
第一,边界很难找。当前并没有一个固定的数据结构,能够快速的识别边界,特别是第三种情况。论文中提到了一种6D BVH的结构来做优化,但迄今为止,边界的快速识别仍然是很难做到的,这非常影响性能。
于是,提供了两种基于reparameterization的方法。我们先看这样一个积分:
如果我们用一个X=x-p,则该积分变为:
此时,当p变化时,等同于我们通过
的方式,不连续性不会跟着p变化,而是采样点会随着p一起变化 。
这里,p(X)采样是独立于变量p的,因此,我们可以从p中取样得到X,然后计算对应的
值,进而计算其对应的gradient。这样,我们并不是要采样那些非连续的位置,而是让采样点和我们想要求导的参数间建立联系,因此绕开了对边界的特殊处理,提升的效率。
另一种方式也是基于换元法,示意如下:
当光源变大时,边界也会变化,通常我们需要考虑边界的不连续问题,如果我们固定区域为初始的范围
,当范围扩大后,通过函数
来确定y的位置
如此,我们通过Jacobian的determinant,将边缘的变化分担到内部权重的变化,从而避免了对边界的考虑(silhouette edge除外)。这样,我们只需要额外计算silhouette edge带来的boundary的不连续。
第二,光路是一个高维积分,每次bounce,都会新增参数,在复杂场景下,通常会有上百万的参数,会有很大的内存压力。
如上图,我们可以把对光路的求导分为两部分:‘emitted’和‘scatters’,我们把emitted的部分记为Q:
一个scattering operator
和一个propagation operator
:
如此,可以得到:
同时,我们想要就算的一条完整光路对应的公式为:
其中,J为Jacobian,对用的是
,
( adjoint rendering)则是当前效果和期待效果的差异,该公式证明在对光路的求导中,仍然满足radiative backpropagation,这样,我们可以从光源出发,也能够从相机出发来scatter
。这里的好处是Q是一个向量,对应场景中全部的参数,而Ae是一个标量,内存消耗少,性能也好。
至此,我们主要介绍了可微分渲染,auto diff,相关的理论基础以及目前主要的挑战和对应方案。
推荐资料:
Physics-Based Differentiable Rendering: A Comprehensive Introduction
Differentiable Visual Computing