环境光照 - PRT和SH

Precomputed Radiance Tranfer + Spherical Harmonics

Posted Kongouuu's Blog on October 25, 2021

PRT和SH

简介

用于Image based lighting, 预计算环境光照的值(prt),这样可以在实时渲染的时候去模拟环境光的影响。

我们常规的ibl是单纯的把环境储存在一个cube map里,这样我们通过渲染方程去让环境贴图形容光照项Li时可以针对区块大小对贴图采样。当然因为渲染方程是积分,无论我们用黎曼还是蒙特卡洛都需要多次采样计算,这样特别的耗时。 所以Games101有提到用split sum预计算,也就是在实时渲染开始前提前对ibl采样的cube map进行一个处理,这样我们针对每个像素只需要对贴图进行单次采样就可以得到渲染方程里面的光照项。

不过ibl的问题一是还是需要采样,二是没有办法对付阴影,所以我们可以采用prt的思路。

基础概念

基函数

基函数就是一系列的函数的加权组合,用于还原另一个函数。主要的用法就是在两个不同的端写入所使用的不同的基函数,这样我们只需要把每个基函数的权值ci写入,就可以通过传递少量信息去复原原本的函数f(x)。

basis-function

函数正交

向量的正交很好理解,就是向量的内积为0。之前学的时候不是很理解函数要怎么样才能算正交。这里会提到这个是因为我们在SH里,所有的基函数都得是互相正交的一个函数,也就是说【我们没有办法通过对其他基函数的权值调整,来替代当前基函数的贡献】。

函数的正交性就是指两个函数,在连续的一个区域内的每个部分相乘后、相加的结果为0。我们把离散的向量内积概念套用到连续的函数上面,因此这里采用的是积分的形式,让积分的两个函数相乘得到的内容为0,不过本质上还是去形容sh的基函数的一个不可替代性。

Sphere Harmonics

简介

球谐函数到底是用来做什么的?假设我们有这么一个函数f(x),他可以画出一个三维的物体,跟球比较相似。我们假设这个球体通过函数绘画出来后,表面的每个面到球的中心点的长度最多为1,这就是一个球谐函数。具体画出来的样子可以是这个样子的:

这是一个以球面为基础,并且对特定的面的长度短于1的这么一个形状。 这个形状他是可以通过一个函数画出来的,也可以用来表示光照。也就是长度为1的地方光照的值为1(RGB单个通道的值为1),0的地方为没有那个通道的值。因此如果我们只用这么一个函数就可以近似一个cube map来代替cube map的环境光。【Cube Map的环境光也不过是表示一个球体上面各个方面的rgb值,通过rgb三个通道的球体模拟,我们完全可以达到近似的效果】

接下来就是怎么去把代替光源的球谐函数从precomuted的文件传入我们的shader,或者说我们的实时渲染管线里面。当然像上面那么复杂的图形,函数也会特别的复杂,我们没有办法直接的去传递这样的一个信息,因此我们也需要用到基函数。

基函数和球谐函数

根据基函数的思路,我们可以拿一大堆基函数B(i),然后各自给一个权值C(i),然后把基函数各自套上权值后相加就可以得到近似一个函数的效果。那么我们这里需要的就是通过一大堆基函数相加,来得到一个球形,也就是上面在做的事情。 图片上的每个球都是一个基函数能画出来的形状,当我们把所有的图形加在一起,那么我们就能获得一个近似完整的球的这么一个形状。

也就是说,我们知道这9个形状【3阶的球谐函数】是怎么画的,那么对于每一种不一定的环境贴图的近似,我们只需要去记录每个图像对应的权值C(i)。当我们知道了每个C(i)的值,我们理所当然的就能复刻出整个环境光照的情况。

PRT

BRDF

我们重新看一下brdf计算的公式:

  • 光照项可以通过传递的球谐函数权值,把点的normal朝向形成一个半球,在半球内根据基函数进行蒙特卡洛计算
  • Visibility在这里跟球谐函数没有关系,如果是静止的场景也可以提前计算遮挡。
  • BRDF项如果我们拥有每个顶点的法线值,我们完全也可以预计算。

上面的这个思路是可行的,不过并不完全。我们的蒙特卡洛计算实际上是特别的耗时,但这一个步骤他并不能放在离线的部分。我们的PRT实际上是可以做到光源旋转的,这一点并不难想象,假设我们有一个能模拟成光源球体的函数,我们只需要对函数旋转就能旋转光源。 但如果我们离线计算了每个着色点的值,我们就达不到旋转光源的一个效果。这一部分是可以优化的,我们其实并不需要在每个点根据立体角去反推L(i)的值。

实际上我们可以把这一部分拆开。我们对球体的形容权值C(i)是不会变的,唯一会变得就是代入基函数的(wi),也就是立体角。这一部分需要计算的区间跟brdf需要的是一样的,所有我们把B(i)跟BRDF一起当作Transport项针对每个顶点去进行预计算。

最后我们实际上离线计算好的东西就是2部分

  1. 球谐函数的9个系数C(i) 【假设使用3阶的球谐函数那么就是9个系数,如果使用更多阶的话就是更多系数】
  2. 每一个顶点的9份transport值。

因此我们传入渲染管线时只需要让系数内积对应顶点的9个transport值就可以得到渲染方程的一个结果了。