【Siggraph 2002】Precomputed radiance transfer for real-time rendering in dynamic, low-frequency li...

今天分享的是关于Precomputed Radiance Transfer(PRT)的一篇十分经典的文章(见标题),这篇文章引用量极其之高,对PRT算法有着十分重要的影响,原文链接以及示例代码在参考文献中给出。

Abstract

原文给出了一种能够实时渲染低频光照环境下的软影、interreflections(自反射?)以及焦散现象的新算法,整个算法分成两个阶段:预处理以及实时。

在预处理阶段,会使用一个新的global transport simulator为每个物体生成一个函数(称为transfer function,其实也不是一个,而是沿着物体表面生成了众多的transfer function,每个代表着这一片区域的一个平均光照表现),这个函数是基于物体表面生成的,且能够通过transferred radiance来完成任意的低频输入光的transfer数据的表达,而输入光的transfer数据则包含了软影、自反射以及焦散在内的一大波对光照质量有着重要影响的表现。

而在运行时,则可以通过对物件附近的transfer function进行采样来实现对输入光源的处理,而这些输入光源可以是动态的局部光源,且物件本身是支持刚体运动的(光源的位置也可以动态改变)。

光源以及transfer function都是以低阶(比如2阶或者3阶)的Sphere Harmonics(球谐,简称SH)表示的,这样可以消除光照结果的锯齿。

diffuse的积分运算可以精简为9到25个参数的点乘,以极低的消耗得到较高的渲染质量。
光滑物体(specular)的光照计算则会通过矩阵运算来模拟(为什么不用vector点乘了?)。

后面还介绍了如何通过一个预处理的物件来为周边空间点生成动态光照环境的radiance transfer function,在这个基础上就可以得到刚体运动的物体向任意动态的物体投射的软影以及焦散了,且这些计算都是可以保证实时帧率的。

1. Introduction

面光源、软影以及自反射都是全局光照效果中十分重要的元素,然而现存的方法都无法做到实时效率。

实时运算需要解决的问题有:

  1. 复杂的随着空间而变化的BRDF的建模,而这需要对空间的每一点沿着上半球面进行积分,且这个过程需要考虑遮挡以及投影关系等,十分复杂。在这个课题上有众多的研究,但是最终都没有一种能同时满足质量与效率的解决方案。
  2. 输入光照的积分问题,有人给出了一种解决方案,即通过对输入光源的radiance进行采样之后与一种特定尺寸的kernel进行卷积(离线完成)来实现积分的离线运算与实时获取,这种方案能够解决一些问题,但是还不够完善,比如没有考虑到物件对光线遮挡情况下的阴影信息等(复杂的光照传输(transport)问题)
  3. 复杂的光照传输问题,也有方案可以实现对复杂几何场景下的光照传输情况的模拟,但是这些方案在光照积分上又存在问题,因此对于大型光源而言就不实用了。

原文就是希望给出一套实时的能够解决上述三个问题的方案,其中的关键在于,这里不再执着于完美的模拟光源表现,而是只实现对光源低频数据的记录与模拟。通过低阶SH来表达光源环境,同时也通过低阶SH来表达输入光源作用下光照是如何影响到物件以及物件周围区域的。

为了对原文的算法进行描述,这里先给出一个简单的示例说明,比如我们有一个凸多面体,这个凸多面体正被一个代表无穷远处光源的环境贴图(cubemap)照射,如果只考虑diffuse反射的话,那么这个凸多面体在输入radiance的作用下经过自身的处理逻辑将之转换为输出radiance,而这里的转换处理逻辑,我们就称之为transfer function。

在这个示例中,每个点的transfer function对应的就是沿着法线上半球面各个方向一个乘上的积分,而对于一些更为复杂的情况,比如并非凸多面体,而是存在内凹的面片的物体上,积分函数还需要乘上一个代表这个方向遮挡信息的visibility项。

然而阴影遮挡等一系列会影响到transfer function的量可能会十分复杂,放在运行时去评估会极大的增加计算消耗影响应用的流畅性,因此这里给出的方案是将这些复杂的transport simulation放到离线完成,最终将物件表面上各个点的transfer function表示为向量或者矩阵,同时,由于输入光源的radiance在运行时的评估并不会十分消耗(是吗?),目前的显卡可以通过对多个采样点进行采样来完成对输入radiance的计算,在此过程中,一些解析模型(比如天光模型等)可以用来辅助完成这个目标。

原文提出的方法,会用一些线性基函数(linear basis,比如SH)完成对输入光源的radiance以及transfer function的表达。在这种表达方式下,原来输出radiance计算的积分运算就可以转换为一些简单的乘法跟加法运算:

  1. 对于diffuse而言,transfer function跟input radiance都可以几个SH基函数的系数(9到25个系数)组成的vector就能够表达,而输出radiance的计算就转换为两个vector的点乘了
  2. 对于高频的specular而言,transfer function就不能只用少数几个参数组成的vector来表达了,而是需要表达成更多参数的matrix,input radianc则还是表示成系数vector,输出specular radiance则可以通过vector与matrix的乘法来得到。

上述过程可以在一个pass中完成,且不论场景跟光照多复杂,其计算消耗都是固定的。

在这个技术中,软影等复杂的光线传播行为被抽象成transfer function,而这些都是可以在离线完成的,不占用运行时的消耗,因此可以实现实时全局光效果,这个思路不但支持surface gometry同时还支持volume geometry。此外,如果采用的SH阶数较高(5阶25个系数就足够),使用这种技术还能模拟一些较为光滑表面的反射(但是想要支持镜面反射其消耗就过高了难以做到实时)。这种技术除了能够支持rigid body本身产生的光线传播模拟(self-tranfer),同时还支持对周边区域的光线传播模拟(neighbourhood-transfer,包含软影、自反射、光滑反射以及焦散在内的多种现象)。

算法概览
整个算法分为两部分,分别是预处理部分与运行时部分:

  1. 预处理部分:在预处理阶段,对于每个需要应用此方法的模型(逐模型还是逐模型实例?推测此处并未考虑场景其他物件对模型的影响,只是计算模型本身的影响,因此只需要逐模型计算即可),会为模型上的每个采样点(通常是顶点)计算一套SH参数(diffuse模拟只需要一个SH系数组成的Vector即可,specular模拟则需要一个matrix),这套参数代表了光线在打在此采样点上的转换情况的transfer function。
  2. 运行时部分:在运行时,每个采样点位置的入射radiance会被投射到SH空间,得到一套SH系数组成的vector。之后diffuse反射只需要将之前的SH系数vector跟光照SH系数vector点乘就能够计算得到;而specular则会需要将之前的transfer matrix跟光照SH系数vector相乘,得到的是代表一个SH函数的SH系数vector,这个vector代表的是光照在每个点位置处的self-scatterred的入射radiance,而要想得到光滑表面上此点的具体反射结果,还需要将这个vector代表的SH函数与物件的BRDF进行卷积,最后根据视线与法线计算出反射方向,将反射方向代入到卷积函数中才能得到。

2. Related Work

3. Review of Spherical Harmonics

3.1 基本概念

SH是定义在球面空间的一组正交基,跟定义在一维的圆上的傅里叶变换很相似,其参数化逻辑可以表示为:

SH基函数则定义为:

其中是伴随勒让德多项式(associated Legendre polynomials),而是归一化常量:

上面定义的是一组复数域的基函数,实数域的基函数就是在这个基础上经过如下的简化而得来的:

上述公式中表示的是SH的band index(阶序),越小表示其表达的信号的频率越低,此外每一阶的基函数最终都可以被简化为的次多项式(比如就是二次多项式)。

3.2 Projection and Reconstruction

因为SH基函数是相互正交的,因此定义在球面上的任意标量函数(函数值是标量)都可以通过如下的积分计算出其在对应基函数上的投影系数:

而通过这些系数,我们也可以重建出对应的近似函数:

上述近似函数会随着阶数的增加而提升精度,一些低频信号只需要几个低阶SH就可以完成高度模拟,而一些高阶信号如果使用低阶SH则会存在较高的精度损失,前n阶SH的基函数数目为,如果将前面的求和转换为一维的,那么可以写成如下形式:

其中。

3.3 Basic Properties

SH基函数的一个最为关键的属性在于其旋转不变性(rotational invariance),也就是说,给定一个函数g(s) = f(Q(s))(Q表示的是球面S上的任意的旋转变换),那么我们有:

翻译一下,也就是一个函数在对坐标系进行旋转变换后,其SH模拟近似函数可以通过对坐标系进行同样的旋转变换而得到。

在应用层面,这个特性就意味着当函数f的采样点是某套采样点的多个旋转copy的集合的话,最终的计算结果也不会存在锯齿等高频噪声。

SH基函数的正交性则意味着,任意两个函数f跟g,他们的近似模拟函数相乘的积分则可以表示为系数的点乘:

3.4 Convolution

这里将一个圆周对称的kernel函数h(z)与一个普通的函数f的卷积表示成h * f。

这里需要注意的是上述kernel函数的限定条件为圆周对称,在圆周对称的情况下,函数可以用Zonal Harmonics(可以表示为自变量只包含z的函数,对应于SH中m=0的那一列基函数)而非Spherical Harmonics来表示,这个限定条件是为了使得最终的结果依然可以用SH来表示(否则就需要使用更为高维度的rotation group来表示了),而卷积函数的SH表达则会满足如下的公式:

这个公式说明了,上述卷积函数的投影系数是两个函数的投影系数的乘积(h(z)使用的系数是,由于是ZH表达的,每个band也就只有这个系数是非0的)之后乘上一个常量。

利用这个卷积特性,我们可以实现环境贴图与半球余弦kernel(h(z) = max(z, 0),其对应的系数可以通过公式计算得到)的快速卷积计算以完成对irradiance map的求取,此外卷积特性还可以用一个更窄的kernel来完成prefiltered environment map(specular使用)的计算。

3.5 Product Projection

现在有两个可以用SH表达的函数a(s)跟b(s),其中a(s)是已知的,b(s)则是未知的,令c(s) = a(s)b(s),那么c在SH上的投影系数为:

注意上面公式中的只是从众多个SH系数中抽取出的一个,是为了方便叙述特意做的简化,实际上的的计算中应该是要包含两个求和计算的。这个公式的意义是说c(s)的系数可以表示为b(s)的系数向量在矩阵上的线性变换。

这个公式中的变换矩阵是一个对称矩阵,其每个分量可以通过从Clebsch-Gordan级数(series)中推导出来的递归算法计算SH基函数的三重积(triple product)积分得到,当然也可以先不将a(s)转换成SH投影近似函数并通过数值积分计算得到。

最后需要注意的是,两个n阶的函数其相乘的SH投影函数阶数上限为2n-1。

3.6 Rotation

对一个近似函数的旋转,可以通过对原函数的系数进行线性变换来求得其SH投影系数(旋转不变性),且这个旋转过程中各个band之间的系数是独立处理的(相当于能量变换只发生在band内部,不会出现band之间的能量流动)。

线性变换矩阵最简单的求取方式就是借助zyz的欧拉角来完成对旋转矩阵Q的分解,这个分解会需要用到一个十分复杂的递归公式来得到。不过由于我们通常只关心一些低阶的情况,因此这个旋转实现还可以通过符号运算方法完成。

4. Radiance Self-Transfer

Radiance Self-Transfer指的是用于描述物件对自身产生的阴影与散射的Transfer Function。

为了描述这个过程,我们用O来表示物件,并用来表示O上各个采样点p的入射光,每个点p的入射光radiance可以用一套SH系数vector来表示,这个vector我们称之为,我们会通过对物件表面或者极端情况下只对一个点进行动态、稀疏的采样(具体如何做呢?)来求得这个vector,这里的假设是,如果没有物件O存在的话,光照在这个地方的变化是非常小的。

物件本身的Transfer Function的采集过程相对于光源radiance而言则是预计算的且细密的,最终会输出一系列针对物件上各个采样点(通常是顶点)所对应的vector或者matrix。

Transfer Vector 通常用于diffuse表面,这个向量用于描述在光源输入作用下输出一个标量radiance的线性变换过程:

换句话说,的每个分量表示光照向量上对应基函数在点p处对shading的影响。

Transfer Matrix 则是用在光滑表面上,表示的是对于输入光照SH向量的线性变换,变换的结果依然是一个SH向量,这个向量表示的是这一点上的输出Radiance效果(因为光滑反射的结果会随着视角而变化)

输入radiance跟输出的区别在于,前者是不考虑物件O的情况下的radiance(相当于移除物件O后的Radiance),而后者则是考虑了物件O对输入Radiance影响后的Radiance(也就是添加了物件O对输入Radiance的Transfer)。

矩阵的每个分量表示的是输入光照RadianceSH投影的第j个分量对输出radiance的SH向量的第i个分量的线性影响。

下面介绍一下Transfer Vector跟Matrix是如何推导与计算的。

4.1 Diffuse Transfer [transfer vector for known normal]

在物件O是diffuse表面的情况下,我们可以按照光照效果的自然与计算复杂程度将Transfer Function的计算分成三步,分别是unshadowed、shadowed以及interreflected,这三种情况下的光照效果依次递增,而计算复杂度也同样依次递增。

4.1.1 Unshadowed Diffuse Transfer

不考虑物件本身对采样点p的遮挡情况,在p点处的Transfer Function可以表示为:

其中表示的是点p处的albedo值,是不考虑物件O本身的遮挡情况,在方向s上的incident radiance,则是点p处的法线,表示的是方向s上的光照反射比例。

这是一个标量函数,自变量为,即一旦输入光的radiance确定,那么每个点的就确定了,此外可以看到,文章中的Transfer Function(中文用传递函数,下同)其实就对应于当前点的输出Radiance,难道不应该是实现输入Radiance到输出Radiance的中间转换过程吗?实际上,传递函数指的是对的处理过程,也就是这里的球面积分,由于输入光源不确定,因此在预计算的时候,我们会选择SH基函数作为的替代,得到一组SH投影系数,并组成一个向量,这也就是所谓的Transfer Vector了。

通过将跟投影到SH空间,上述这个公式就可以转换为二者的SH系数vector跟的点乘了。

对于每个点p而言,有是已知的,因此其transfer function对应的SH投影系数向量是可以预计算的,而实际上,在给定了的情况下,是有一个现成的解析公式可供使用的,不需要再将此数据存储下来。

可以看到,表示的transfer function是一个低通滤波器,因此这里不需要高阶的SH就能够得到很好的模拟精度,通常只需要三阶9个SH系数就能够表示任意情况下的光照环境。

4.1.2 Shadowed Diffuse Transfer

考虑物件本身带来的遮挡关系,前面的公式可以改写成:

可以看到,其中只新增了一项,这一项表示的是在方向s上的遮挡情况,当无任何遮挡时,数值为1,否则为0。

跟无遮挡情况一样,这个公式也可以转换为两个SH系数向量的点乘,不同的是,新的传递函数还需要考虑Vis的影响:

跟前面不同的在于,这里预先计算得到的Transfer Function的SH系数向量需要存储在每个采样点p中,这是因为V(s)是不固定的,因此没有一个解析式可以直接使用,另外,由于V(s)可以是十分高频的数据,因此使用低阶的SH可能精度上不太够,通常来说,5阶或者6阶(25或者36个SH系数)基本上能够满足所有需求。

4.1.3 Interreflected Diffuse Transfer

前面Shadowed Transfer Function将被遮挡方向上的贡献全部归零,但是实际上在这些方向上还是有输入数据的,输入光强来自于这些方向上遮挡物的反射光强,考虑这一项数据之后,公式可以更改为:

其中指的是方向s上的遮挡点的反射光强,这个公式计算的复杂性在于除非输入光源距离物件O无穷远,此时可以将O上各点的输入radiance都近似看成是不变的,此时在任意点的输出radiance就可以通过计算得到,之后再将这个输出radiance代入到中去计算上述公式的输出,而上述公式的积分就可以跟之前一样转换为两个向量的点乘。

但是,如果输入光源是局部光源,那么这个假设就不满足了,计算结果就会十分复杂,这里就很难显式给出intereflection的transfer function,第五章中将会介绍如何通过数值方法来完成相关计算。

4.2 Glossy Transfer [transfer matrix for unknown direction]

光滑表面的Self-Transfer函数也可以通过同样的方式得到,不过不同的是,这里的函数不再是与固定的法线相关,而是会随着视角(反射向量)而变化的。跟前面diffuse部分定义的H kernel函数相似,这里我们也可以定义一个光滑反射kernel函数G(s, R, r),其中R表示的是视线的反射向量,r表示的是表面的粗糙度,这个参数关系着镜面反射的广度,虽然原文作者认为是可以通过SH投影系数完成对任意BRDF的表达,但是在这篇文章中没有对这个结论进行验证。

跟Diffuse一样,Specular也有三种Transfer Functions:

光滑表面的Transfer Function最终会在视线反射方向上输出一个与以及R相关的标量Radiance函数,而由于Transfer Function的自变量不再是仅仅只有s了,所以就没有办法再用一个SH系数向量表示了。

不过这里并没有将Transfer Function转化成基于R跟r的标量Transfer,而是采用了另外一种更实用的分解方式,即将输入的radiance变换成a whole sphere of transferred radiance(覆盖整个球面的输出Radiance?什么意思)(具体过程下面有说),假设光滑表面的kernel函数G是沿着反射方向R圆周对称的(比如Phong模型就是),那么就可以跟进行卷积之后得到一个卷积函数,之后将反射向量R放入到卷积函数就能够得到最终的结果。(这里为什么不使用跟Diffuse一样的标量Transfer,所谓的更实用到底实用在哪里?这种分解方式的理论依据是什么,怎么推导的?)

上面过程中有一个地方需要详细解释,那就是如何将转换成,这个过程是通过一个Transfer Matrix而非Transfer Vector完成的,比如带阴影的Transfer Matrix(这里没有给出推导过程,后面有机会再来补上)的作用可以表示为:

其中矩阵就是前面3.5节中的矩阵,也就是我们需要提前计算的transfer矩阵。需要注意,这里的输出结果并不是最终输出的Radiance,最终输出的Radiance应该是一个标量,实际上是这里的输出结果与物件的BRDF进行卷积后使用观察视线的反射向量赋值后得到的结果。那么这里输出的Radiance到底是什么,为什么这里需要添加一个变换过程而不能直接使用原始的输入Radiance跟BRDF进行卷积,原文没有说。

在光滑反射计算上,即使输入光源频率再低,随着表面光滑程度的上升,精确表达所需要的SH阶数也就越多,为了节省计算消耗,通常会考虑使用非正方的矩阵(比如25x9)来将低频的输入光照信号转换为高频的transferred radiance。

对于带阴影(非interreflected)的光滑反射,依然可以通过一个向量而非矩阵来完成transfer function的表达,这是通过在需要的时候(比如运行时)将矩阵跟相乘来实现的(听起来好像并没有做太大的改变?可惜原文没有做相应的实现,不知道这里理解对不对)。

4.3 Limitations and Discussion

目前这套算法存在如下几个不足或者限制:

  1. interreflection相关的transfer需要在预计算的时候将材质信息一起bake进去,因此不支持运行时材质修改,而shadowed则是可以支持运行时材质更改的
  2. 如果场景中有其他的遮挡体或者光源本身跟物件O的凸包围盒穿插在一起的话,会导致预计算误差上升。
  3. 预计算后的物件只能发生刚体运动,不支持形变以及物体上面的部分组件分离等动态变化
  4. 目前的计算都是在光源radiance在物件O周围的变化基本可以忽略的假设上完成的,因此这也是一个限定条件
  5. Diffuse Transfer在计算过程中已经完成了跟表面BRDF的卷积(之前与半球面上的cos权重函数的乘积积分),而Specular Transfer则没有经历这个过程,因此需要在运行时与BRDF(前面介绍的光滑表面的kernel函数G)进行卷积来输出output radiance,当然我们也可以在离线的时候与一个固定的BRDF进行卷积,并将结果缓存下来,但是这种做法会导致灵活性下降(不支持运行时BRDF更改?好像也不是什么特别大的事情?)

5. Precomputing Radiance Self-Transfer

这一章主要介绍离线Radiance Self-Transfer是如何计算的。

此算法通过在物体O表面使用基于一个无限大半径的球面作为光发射器的SH基函数来实现GI(全局光,下同)模拟,使用的是gathering(而非shooting)方式的updating策略,而gathering策略本身可以通过多种方式实现加速,需要注意的是SH系数是有符号的。

GI模拟会将输入光转换为n阶的未知SH投影L(通过个系数表达),虽然这里光源是未知的,但是我们在计算中实际上并不需要这个数据,只需要投影函数就能完成(运行时再乘上相应系数即可)。

整个离线计算过程分成多个pass:

  1. 第一个pass用于统计从光源出发到与物件O碰撞的直接光照
  2. 后续的pass则用于搜集从当前物件上的其他位置到当前点的间接光反射

在上述的每个pass中,每个采样点p都会进行能量的gathering(大尺寸的光线发射器,比如说低阶的SH,使用gathering更新策略速度要快过shooting更新策略,具体原因这里给了个参考文献,后面有机会再来补充),

为了表示O上某个采样点p的球面方向,这里会生成大量(10k到30k)的随机(射线采样)方向,用集合{}表示,对于集合中的每个方向,都会计算每个基函数的数值(系数)。

为了对算法进行加速,会按照层级关系进行组织,层级中的每个节点称之为一个bin(每个bin包含对应级别下的多个),层级是按照二分的方式建立的(每两个同级bin合成一个更高级的bin,之所以使用二分而非四分,是因为在球面上四分无法如平面一般得到平均划分,而二分则可以),最终通过6到8级划分,得到512到2048个bin。

在第一个pass中,我们会在采样点p上,以法线方向作为半球面的凸面朝向,发射shadow射线(射线覆盖整个球面,而非半球面,当然在实现层面,下半球面的射线结果可以直接返回):

  1. 通过层级结构,剔除掉那些处于半球面之下的方向
  2. 为每条射线添加一个occlusion tag,,这个tag会用来表示这条射线是否(与物件的其他位置)发生碰撞
  3. 每个bin也会有一个occlusion tag,用于指定此bin中的所有射线是否处于occluded状态

通过上述的tag,可以方便后续interreflection的计算。

Diffuse表面上点p的transfer向量可以通过下面公式计算(表示可见性,表示的是方向s上的光照反射比例,两者相乘之后就是完整的transfer function,在SH上的投影就得到transfer向量):

Specular表面的transfer矩阵则对应下式中的:

变换后的输出radiance是一个函数,其在SH上的投影系数为:

是通过三重积积分得到,是一个对称矩阵。

SH投影系数的计算都是通过对所有的方向结果进行累加来模拟数值积分而得到:
其中diffuse是通过如下的公式计算得到:

光滑反射矩阵系数则通过如下公式计算得到:

这些系数初始化都为0,通过累加实现积分(上标0表示第一次迭代,对应shadow pass)。

diffuse系数计算是通过如下公式推导而来的:

光滑表面矩阵系数则是通过如下公式推导而来的:

之后就进入了interreflection pass,这些pass会对shadow pass中标注有occlusion tag的bin进行遍历,如下图所示,如果与O上的另一点q发生了碰撞(p、q两点相距很近),那么我们就可以沿着的反方向猜到q点的radiance数据。

按照这种做法采用下面的公式就能计算出对应的系数(两个公式中的b都指的是bounce times,也就是反弹次数,且跟前面shadow pass中的一样,这里也是先将所有的系数初始化为0):
对于Diffuse而言:

这个公式是从下面两个公式中推导出来的:

而对于Specular而言:

公式中的求和部分为在上一次反弹中从q射向p的radiance,因为存储的是入射Radiance,因此需要跟O的BRDF进行卷积才能得到方向的输出Radiance,是第k个卷积系数,反射算子用来计算方向沿着法线的反射方向。

这个公式则是从如下的三个公式中推导而来的:

从最后一个公式我们可以知道,在shadow传递函数中的矩阵是一个对称矩阵,因为矩阵的系数实际上就来源于两个球谐函数的乘积,而系数在交换了i跟j之后,其结果依然维持不变,但是放到Interreflection,这个就不成立了。

Interreflection pass会不断重复下去,直到最终某个pass的总能量衰减到阈值之下,对于绝大多数材质而言,这个过程是十分迅速的。之后将所有的bounce pass中得到的transfer加到一起,就是最终的interreflection的transfer了。

在这个实现框架下,我们是可以实现镜面反射的,不过这里不需要计算镜面的transfer,只需要不断沿着反射方向向前传播,直到遇到一个非镜面反射表面为止,通过这种方式可以在diffuse或者glossy平面上捕捉到能够随着光源动态变化的焦散现象。

上图就是通过这种方式实现的,圆环模型采用的是传统的环境贴图方式渲染的,可以得到较好的光滑反射效果,圆环底部的黄色平面则是采用PRT的方式实现的,是diffuse材质的,整个平面包含了256x256个采样点,最终实现的帧率是130FPS。

6. Run-time Rendering of Radiance Transfer

下面来介绍一下运行时的使用与计算逻辑。

我们现在有一个物件O,上面存储了针对多个采样点的transfer vector或者matrix,在运行时对O进行绘制,需要经过如下一些步骤:

  1. 计算O附近的一个或者多个采样点的入射Radiance在SH基函数上的投影系数{}。这一步可以通过对一个precomputed的环境贴图进行处理,也可以对一个具有解析式的光源进行处理,或者通过对Radiance进行采样来得到相关数据。
  2. 将这些系数变换到物件坐标空间中,并对他们进行混合(是对多个点多套系数之间的同一个系数进行混合吧?比如平均之类……),从而得到一个以O为中心的输入光域(Light Field)。这里的变换在第三章中已经叙述过了,是针对每个物件一次的,这里之所以要进行变换,是为了保证光源数据坐标系跟物件各个采样点存储的传递函数数据所在的坐标系是一致的,从效率考虑,显然对光源进行变换要更高效一点。
  3. 使用上面得到的最终的SH投影系数对O上的点P进行一次线性变换,从而得到输出Radiance(比如Diffuse表面就是点乘,Glossy表面就是矩阵变换)。对于diffuse物件而言,这个数据可以直接存在顶点中,之后通过顶点shader点乘完成相关计算。当然,这个数据也可以存储在贴图中(需要注意系数是有符号的,且范围可能会超出[-1, 1]),之后通过PS进行处理。
  4. 对于Glossy表面而言,上一步得到的只是变换后的Radiance(在VS中完成,可以用SH系数表示),此时还需要再跟物件的BRDF进行一次卷积(放在PS中进行),之后将视线方向的反射向量传入卷积函数才能得到输出Radiance(原文实现采用的是最高5阶25个系数的SH基函数)

一套系数只能表示一种颜色的变换,如果要实现彩色效果,就需要分别为RGB各存储一套,计算的时候也就从一个点乘变成了三个点乘。

6.1 Spatial Sampling of the Incident Radiance Field

在动态运行的时候对输入Radiance进行采样的一个简单而有效的策略就是只在物件O的中心点进行采样。

为了能够处理局部光照效果,一个更精确地方案则是对多个点进行采样,如下图所示:

一套好的采样点可以通过参考文献[28]给的ICP(Iterated closest point)算法在离线的时候给出,这个算法会生成一系列均匀分布的且能够较好代表物件O的采样点,这些采样点位置可以在运行时用来对输入Radiance进行采样。此外,在运行时还可以进一步计算出各个采样点的权重,用于在得到采样点输入Radiance后进行混合(blend)。

6.2 Sampling SH Radiance on Graphics Hardware

这一节会介绍一下如何通过GPU完成对光源Radiance的SH投影系数的计算。

首先,每个光源Radiance采样点处,都会需要沿着一个cube的6个方向进行6遍渲染,得到6张贴图,组成一个cubemap。在渲染过程中,需要移除物件O。

之后通过积分公式对上述cubemap进行积分得到投影系数:

这里可以通过增加一部分离线计算的工作来提升运行时的计算效率,上面是一个积分,这个积分可以转换成求和,示意公式如下:

我们可以令,并对这部分进行预计算(比如对每个方向s都计算对应SH基函数的数值并乘上对应的差分固体角作为权重),并将结果存储到贴图中,之后在运行时只需要对各个采样点,计算出此点的Radiance值,并将这些结果与对应方向上贴图采样的结果进行点乘就能得到对应的系数了。

理论上来说,这个计算最好是放在GPU上完成(为什么?),不过因为精度问题以及GPU上不支持inner product(这是点积的通用扩展,将两个向量按照分量相乘并求和),所以最终还是选择将采样后的radiance贴图读取回CPU,并在CPU中完成上述积分计算模拟以得到SH系数。因为这个原因,radiance贴图的分辨率就要越低越好了。

假设信号是带限的,我们就可以用较低分辨率的cubemap来计算SH的投影,比如带限于六阶球面信号可以使用4x4的贴图来表示,其平均平方差(squared error)为0.3%,最大平方差则为1%,这里的误差是经过归一化的(假设信号是单位功率(unit-power)的,即这个信号在球面上的平方积分(intergrated square)是1)。如果使用6x8x8的贴图,那么平均平方差跟最大平方差则变为0.003%跟0.02%。不过通常情况下,信号都不是带限的,会带有一定功率的高频分量,曾有分析报告指出,假设我们通过对2D贴图进行采样来实现双线性重建(bilinear reconstruction),使用6x8x8的贴图来保存6阶SH系数,其平均平方差跟最大平方差为0.7%和2%,而6x16x16则为0.2%跟0.5%,6x32x32为0.05%跟0.1%。

如果直接从GPU读取6x16x16的贴图并使用点采样进行计算,那么就会导致锯齿,为了减轻锯齿,这里会考虑将贴图进行(两倍)上采样,并进行一次box滤波,而基函数贴图也同样会经历这样一个过程(预处理阶段),这个过程是放在GPU上完成的,在PIII-933 PC with an ATI Radeon 8500硬件上花费是1.16ms。

7. Self-Transfer for Volumetric Models

体素模型的Self-Transfer函数的计算逻辑跟surface模型是一样的,同样是在预处理阶段通过将基函数作为emitter来求取对应的传递函数,在运行时通过改变输入光源就可以得到不同的光照效果,这里就不做展开介绍了,有兴趣的同学可以移步原文。

8. Radiance Neighborhood-Transfer

Neighborhood-Transfer用于在预处理阶段计算出在低频参数化光照下物件O对周边环境的影响。Transport模拟过程跟Self-Transfer完全一致,只不过这里不仅仅只是需要考虑物件O上面的Transfer而是同时需要考虑O周边的采样点的Transfer。在运行时,可以将任意的其他物件R放置在O的周边以实现O对R的实时阴影、反射以及焦散的计算,在这种模式下,我们可以计算出一个运行中的汽车O对地面R的投影效果,这这些效果同样可以随着光照环境的变化而动态变化,另外,通过对光滑transfer以及动态光照的支持,还能够实现比参考文献[15]中的irradiance volume方法更为通用的效果。

由于在预计算过程中R是未知的,因此O的Neighborhood-Transfer需要存储一个Transfer Matrix,即使对于Diffuse Receiver而言,也需要存储矩阵而非向量,这是因为我们不知道R的法线的具体信息的原因,原文的实现是将O周边的3D空间划分成一个Grid,Grid上的每个点都预计算一个Matrix。在运行时,我们会根据下面公式在CPU中为每个点完成matrix transform得到transferred radiance:

之后将结果上传到GPU,这个结果实际上是一个volume texture,每个像素存储的是transferred radiance的SH系数。

之后再PS中使用transferred radiance完成相应的光照计算,比如Diffuse Receiver就会将这个radiance与加权后的cosine函数在半球面进行卷积,将法线代入到结果中就行了;光滑Receiver则会需要通过如下公式进行计算:

如果R有对应的Self-Transfer,可能会使得计算过程变得复杂,因为O跟R的数据不是在同一个坐标系下计算完成的,因此在运行时需要对采样点的结果进行旋转,原文中说到当时(2002年)的硬件条件不足以支持实时计算,不知道现在的硬件条件是否支持。

与Self-Transfer方法相比,Neighbor Transfer的计算方案还会引入一些其他的误差:从多个物件向R投射阴影或者反射会增加计算的难度,除了O跟R之外的其他物件导致的局部光照的变化也会是一个问题,在O周边的区域中,光照需要保持基本不变才能得到一个较为精确的结果,尤其需要注意,当O跟R周边有其他物件与O发生了穿插,会导致阴影缺失等异常情况出现,此外,O的Grid Volume要足够大(至少要能够包裹R)才能使得结果自然准确。不管怎么样,Neighborhood Transfer方法做到了此前其他方法在实时条件下无法做到的效果。

9. Results

整体的渲染消耗如上图给出的表格所示,除了需要矩阵运算的效果之外,其他的运算在当时的硬件上基本上能够做到实时,而矩阵运算效果在固定相机或者光源之中的一个时,也能实现实时运算。

从效果上来看,能够得到较好的self-shadow跟反射特性,且不会出现传统shadow map的相关瑕疵(比如随着相机转动而抖动等),只是会有一些模糊,结果看起来就跟对输入光源进行模糊处理一致。

上图给出了不同SH阶数下的阴影表现,光源是圆形光源,只是分别使用了不同的角半径(angular radius),在角半径较小的时候,对应小尺寸光源,可以看到想要得到较好的阴影效果,需要较高的阶数,而大尺寸光源则需要较少阶数就能得到不错表现。需要注意的是,最终渲染的质量不仅仅取决于SH阶数,同样还受几何形状的影响,通常需要三阶以上的SH才能表达。

因为旋转不变性,跟此前的方法相比,SH表达在低频光源环境效果要好一些,因为这个特性,在对光源radiance进行动态采样的时候,会消除其他方法中会遇到的shading wobble瑕疵,不过如果光源在高阶SH上的能量分布比较多的话可能会导致Ringing/Gibbs问题,而这个问题在简单模型上比较明显,不过这个问题可以通过窗口函数来降低高阶SH上的能量分布来缓解。

10. Conclusions and Future Work

参考文献

[1] Precomputed radiance transfer for real-time rendering in dynamic, low-frequency lighting environments
[2] Precomputed Radiance Transfer Implementation

你可能感兴趣的:(【Siggraph 2002】Precomputed radiance transfer for real-time rendering in dynamic, low-frequency li...)