Bootstrap

(个人学习用)球谐光照、PRT、LPV、VXGI

https://zhuanlan.zhihu.com/p/23452210997

GAMES202高质量实时渲染-个人笔记:实时全局光照 - 知乎

【论文复现】Spherical Harmonic Lighting:The Gritty Details - 知乎

【论文复现】An Efficient Representation for Irradiance Environment Maps - 知乎

1.球谐函数

1.1 定义

球谐函数是系列定义在球面上的二维函数(一个定义在球面上的函数可以看作是从球面上的点到某个数集的映射)。

图的最左边标明了球谐函数的阶数,第 l 阶球谐对应有 2l+1 个基函数,前 n 阶一共有 n2 个基函数,而图的最下边 m 则对应了球谐函数的序号,第 l 阶对应的序号范围为 [−l,l]

1.2 基函数

对于复杂的函数,我们可以通过各种方法比如泰勒展开,广义傅里叶展开。把一个复杂的函数展开成简单的函数如:

这里的 P(x) 就是基函数, fi 是不同基函数的系数。这里的展开是一种近似,只有展开阶数达到无穷大时才能完全等于原函数,因此N越大对原函数还原的越好。球谐函数也是一种基函数,因此任意一个函数也能表示成若干球谐函数乘以其系数的加和形式:

1.3 投影

基函数前面的系数为常数也叫做球谐系数,生成球谐系数的过程,也称为投影,其计算方法如下:

其实就是一个用原函数f(x)乘以对应项的球谐函数Ylm在整个球面空间积分。

1.4 特性

  • 正交完备性正交完备性如下,不用管具体Y是什么以及怎么算,只需要知道必须两个球谐函数一模一样才为1否则就为0即可。这个性质是球谐函数用来简化计算的核心。

  • 旋转不变性这里的旋转不变目的就是环境光照变化之后我们只需要简单的计算就可以得到光源旋转之后的结果。这个性质使得球谐函数适用性更加的强。

2.球谐光照

球谐光照最主要的应用就是用于计算环境光,这里的环境光指天空盒/天空球所发出的光。

2.1 漫反射环境光

漫反射光照的计算公式:

先把积分内的函数分成两个部分:

分别用球谐函数展开:

代回光照公式得:

因为球谐系数都是常数,可以提出积分:

根据球谐函数的正交完备性,积分内两个球谐函数只有完全相同时才为1,其余都为0,进一步化简:

接下来就是求两个球谐系数的步骤。首先来看 Li

这一项要求球谐函数与入射光线乘积在整个球面上的积分。这里的入射光虽然是着色点p的函数,但是由于使用天空盒作为环境光输入,其强度与着色点位置无关。因此通过蒙特卡洛方法,采样各个方向天空盒的辐射强度和球谐函数值即可。

for(pixel &p : Cubemap)
    Li += p.color * Yi(normalise(p.position)) * dw;

接着看 ti

与 Li 不同,这里积分函数中的法线是与具体着色点有关的,要想预计算这一部分,只能对于每个法线方向都计算一遍积分数值,再将这些结果存储在一张纹理中。考虑到一般使用三阶球谐函数来展开,因此一共有九个系数需要计算,需要三张纹理存储(RGB每个通道存储一个系数)。

因为使用的是天空盒,采样时采样了六张贴图,同样保存预计算结果也需要六部分,共计18张纹理。

for(normal &n: sphere)
{
    for(pixel &p : Cubemap)
        Li[n] += dot(n,normalise(p.position)) * Yi(normalise(p.position)) * dw;
}

然而实际上并不需要这样耗费存储空间存储每个法线的结果,这需要利用到球谐函数的另一个性质。

推导过程看不懂。。。

利用旋转不变性可以将光照公式改写为:

  • 首先对于 Llm 而言,就是上文中 Li 。
  • 对于 tl ,之前需要针对每一个法线方向预计算出一组 tl ,而本文 tl 已经变成了常数与 Ylm(n) 的乘积。

  • 最后看 Ylm(n) ,注意他的参数是具体着色点的法线 n 。那也就是说完全可以在着色器中根据顶点着色点传过来的法线 n 计算Ylm(n) ,然后再与外部提前预计算好的 Llm 和 tl 进行简单乘积就得到最终的漫反射环境光。

2.2 镜面反射环境光

同样先看光照公式:

同样分成两部分:

前半部分与漫反射公式完全一样,后半部分使用IBL相同的方法进行分解计算,根据不同的参数预计算化简结果存入纹理中:

2.3 对比IBL

3.预计算辐照度传输PRT(Precompute Radiance Transfer)

3.1 简介

PRT(Precompute Radiance Transfer,基于预计算的辐射传输),其核心思想是将渲染方程分为lighting(环境光项)和lighting transport(Visibility&brdf)两部分,假设场景中只有lighting部分会发生变化(旋转or更换光源),而lighting tansport部分固定不变。因为lighting部分可以被球谐展开,所以旋转操作并不会造成多大影响(旋转不变性),即使更换光源,也只要在预计算中多计算几个光源就行,那么这两部分就都可以被球谐展开,从而在预处理中完成部分计算。

3.2 Diffuse case

考虑可见项渲染方程:

对于着色点为diffuse材质的情况,其brdf项为常数

将积分函数分成两部分,分别球谐展开:

代回原方程:

此时与球谐光照计算漫反射环境光几乎一致了,可以使用相同的方法计算。预计算结果相当于计算了两个与球谐阶数有关的向量,实际渲染时只需取出预计算结果进行一次向量点乘即可。

3.3 Glossy case

对于glossy材质,其brdf不再是常数,而是与出射方向相关的函数:

对 Ti(o) 球谐展开:

此时预计算结果不再是一个向量,而是与球谐阶数、出射方向相关的函数,可以预计算为一个矩阵(纹理)。

因此对于相同阶数的球谐函数,Diffuse材质预计算两个n维向量,而Glossy材质预计算一个n维向量和一个n维度矩阵。

3.4 总结

纵观PRT的整个计算过程,不管是哪种计算方法,它都只需要经过部分的预计算,就能把原先的逐顶点 / 逐像素操作转化为向量的点乘或向量与矩阵相乘,这对于性能来说绝对是成百倍的提升。它不仅适用于计算带阴影的环境光,还适合做多次bounce的间接光照预计算。

PRT虽然可以很好的实现带有阴影的实时环境光渲染,但它也有它自己的局限性,主要体现在两方面

首先是对场景的要求。之前我们假设light transport不变,相当于默认了这一部分对于场景是一种自带的属性,也就是说如果要使用PRT,渲染对象就必须具备静态场景、动态光源这一条件,如果场景中的物体发生移动,物体原先的阴影就会因为Visibility项固定不变而留在原地,而如果物体的材质发生变化,或者物体发生了形变,则会得到错误的结果

其次是对材质的要求。因为球谐函数在描述高频信息时需要非常高阶的基函数才能完成重建,所以一般PRT只适合做一些较为低频的情况(试想用26阶球谐重建glossy材质,最后得乘一个 262 阶的矩阵,676*676,这是何等恐怖的储存压力)

4.光照传播体积LPV(Light Propagation Volumes)

4.1 简介

Light Propagation Volumes(光照传播体积),最早是由 CryEngine3 提出的一种基于RSM和球谐函数的实时全局光照技术,其核心思想是通过将场景划分为一系列三维网格(体素化)来模拟光线的传播,随后利用其Irradiance在传播过程中保持恒定这一特征,计算出每个shading point上的间接光照。这一创造性的方法不仅在速度和质量上有着非常好的表现,还同时解决了一部分RSM带来的问题。

4.2 步骤

  • 生成(Generation)

第一步很简单,利用光源的shadow map完成次级光源的定义即可。在这一步可以通过采样对次级光源的数量做出一些简化,从而给算法带来一定优化。

  • 注入(Injection)

对整个场景进行体素化,并将虚拟光源转化为对应体素的属性。一块体素可能对应多个虚拟光源,叠加其内部所有虚拟光源即可得到当前体素的Radiance信息。

在存储过程中,由于Cube Map的开销过于庞大,我们可以使用球谐函数对其进行压缩,工业界一般认为两阶SH就能做到很好的拟合。

  • 传播(Propagation)

对于每一个体素,依据其储存的Radiance分布,计算它周围六个体素的radiance,然后分别对它们使用SH进行简化。对角的体素可以被多步传播覆盖到,因此不做考虑。

计算完成后,再算这六块体素各自除了原体素相邻面以外的 另外五个格子,如此迭代,直至传播的radiance趋于稳定。

  • 渲染(Rendering)

对场景中任意着色点,找到其所在的体素并叠加所在体素中所有方向的radiance后正常渲染即可。LPV提供的这种实现全局光照的方法不需要任何的预计算,因此可以支持各种变化的场景,做到真正的”实时“。

4.3 缺陷

当渲染的几何对象小于体素大小,即体素划分精度不足时,会出现漏光的现象

这是因为在LPV注入的时候,我们假设所有的Radiance都是由体素中心发出的,因此对于下图中原本位于薄墙左侧的点p,就会被强行移动到右侧,产生漏光。为了解决这个问题,我们可以选择提高体素精度,但这还是会面临一个性能与质量之间的取舍。

另外,在场景较大、光源较多的情况下,LPV的计算压力也会随之上升,关于这一点,可以使用类似LOD的级联方法进行加速:

5.体素全局光照VXGI(Voxel Global Illumination)

5.1 简介

与RSM和LPV类似,Voxel Global Illumination(体素全局光照)也是一个基于体积渲染的2-pass实时全局光照算法,但它与二者的区别在于:① VXGI 会在对场景离散体素化之后,再对数据做一次稀疏八叉树划分,所以从一开始,它的“次级光源”就是一系列体素;② 在渲染过程中,VXGI 会采用Cone-Tracing的方式计算间接光的贡献,在速度方面会比LPV慢很多。

5.2 Light-Pass

VXGI 的第一个pass主要关注场景中哪些表面会被直接光照照亮,以及间接光照的反射方向。和LPV的注入过程不同的是,虽然它们都是以体素为储存单位,但 VXGI 记录的是直接光源的入射方向区间和体素对应的受光表面的法线方向区间,结合材质,VXGI 能够轻松的算出间接光照的分布(即直接光出射方向,反射波瓣),而在算完最低层级的体素之后,将当前层级的八个叶子结点相加,就可以计算更高一级体素的间接光照分布了(diffuse的情况比较特殊,下面会提)

比起LPV用SH压缩记录虚拟光源信息,这种计算方法可以支持glossy,并且结果会更加准确。

5.3 Camera-Pass

在第二个Pass,VXGI 就要开始考虑真正的渲染问题了。通过第一个Pass我们可以知道Cone-Tracing的间接光照投射方向与锥角大小,那么对于glossy的表面就可以像Ray-Marching那样,只要基于一定Ray-Cone Footprint(理解为锥截面大小),到八叉树中相应层级进行查询,就可以得到范围内所有shading point的irradiance大小了(其实和mipmap一模一样,只不过将纹理查询转化为了子树遍历问题)

而对于diffuse表面就比较特殊了,VXGI 通常会将出射方向看做若干圆锥,而忽略锥与锥之间的间隙(影响并不会很大),暴力求解,但这样一来diffuse就会比glossy多一层循环,效率比起其他方法自然也会更差

总的来说,同样作为一种体积渲染方法,VXGI 的渲染结果质量非常好,有时候甚至可以做到与光线追踪相近的效果,但缺点是开销太大,且Light-Pass前的体素化会有一定预处理需求,所以它的实际应用也受到了很大的限制。

;