在我的上一篇博客Unity custom shader中调用内置Lightmap和Light Probes中有提到LightProbes以及如何在custom shader中获取LightProbes中所存储的数据。然而,LightProbes中的数据是如何来的我却一无所知,于是我去谷歌了一下,不查不知道,一查吓一跳,这背后有着复杂的理论依据(包括图形学、概率论、信号分析、微积分等),以及令人望而生畏的数学计算,才最终实现了官方文档所说的:
Light Probes provide a way to capture and use information about light that is passing through the empty space in your scene
在此,本篇博客会依据论文Spherical Harmonic Lighting: The Gritty Details尽可能多的介绍球谐光照,当然,有很多地方我也不是很明白,如果有说错的地方望大家指正。
首先,球谐光照在现代游戏图形渲染领域有着广泛的应用,可快速模拟复杂的实时光照,unity中的LightProbes就用了此技术,当然unity中那些不重要的实时光源也用了。所以对于LightProbes来说,其背后就是球谐光照技术的应用。
那么,什么是球谐光照呢?球谐光照(Spherical Harmonic Lighting)从英文字面上来看,是一种光照算法,这种算法的内核是定义在球面上的特殊函数,它可以对光照进行捕捉(capture)并在之后进行重新光照(relight)并且可以实时展示全局光照(global illumination)风格的区域光源(area light)与软阴影(soft shadow)。
既然是一种光照算法,那么论文一开始就是从一个光照公式开始谈起的:
其中:
= 方向的光照在点处所反射出来的光的强度
= 点处的自发光
= 点处的BRDF(Bidirectional Reflectance Distribution Function)
= 从另一个物体上的点传递过来对点有影响的光,并且这道光的入射角度为
= 与之间的几何关系,一般就是,也就是
= 可见性测试,能看见就返回1,否则返回0
对于 、、 、这四项进行实时积分是很困难而且昂贵的,所以我们需要一些数学工具来简化这一积分计算,变不可能为可能!
蒙特卡洛积分
首先我们来看第一个数学工具,蒙特卡洛积分。公式是这样子的:
这个东西的核心思想就是对于一个连续函数f(x)在其定义域随机挑选N个值(这个操作用个词概括就叫采样)并求出N个,除以每个值被挑选中的概率,将他们累加起来除以N,就近似得到了原函数的积分结果。那么对于这个式子而言,采样越多则结果越逼近真实的积分结果。
不过由于这个积分是个球面积分,定义域是在球面上,要在定义域上进行均匀的采样我们还需要一个公式:
其中和是0-1之间的均匀随机变量,他们俩是独立的。是采样方向,在球面上可以理解为维度经度。
用上面这个公式就可以均匀随机的在球面上进行采样,
我们知道单位球面积为,那么随机采样一个点的概率为,
再利用蒙特卡洛积分来近似计算结果。如果有朋友对这个公式怎么来的有兴趣,可以看看Generating uniformly distributed numbers on a sphere这篇博客。
基底函数
信号处理中有个叫傅里叶变换的东西(我没学过信号处理,没法专业的解释这东西,需要专业解释还是请教谷歌吧),简单来说就是对于任意一个函数,可以把它拆成一组三角函数乘以系数之后的和,即。这里的为一组正交基底函数,函数正交意味着有两个函数和 。
我们有了公式后,就要开始找出和。是一组正交基底函数,古往今来已经有很多前辈探索发现了许多的正交基底函数,我们在这里特别关注的是一个叫做伴随勒让德多项式的东西,不过首先我们先把这个放一边,认为是已知的,那么现在唯一要求的是。的求法是在上采样一堆绘制出的曲线,乘以再积分(积分相当于把投影到上来求对的贡献度),得到的值就是我们要求的。这一过程在原文中被称为投影(projection),所以本篇博客中所有出现投影一词时都是这个意思。现在有了有了,我们就可以大致求出原函数,过程如下图。
只要我们采样足够多,选定的基底函数足够多,计算出来的函数就越接近原函数。另外基底函数会通过权重进行排序,一般排在前面的基底函数比较重要,也称为低频部分,它为定下了基调,通常第一个基底函数模拟的是一个平均值,越往后面的基底函数重要性越低,被称为高频部分,展现了的一些噪声部分。
现在可以来看了。之前我们看到了傅里叶变换可以把任意一个函数拆分成一组正交基底函数,是基于x轴的。而我们之前看到的渲染函数却是基于球面的,基于球面的函数一般表达为,两个参数是单位球在两个轴的夹角。在球面上也有类似于傅里叶变换的东西,叫勒让德多项式,特别的,我们会关注一个叫伴随勒让德多项式的东西。根据Wikipedia,伴随勒让德多项式与勒让德多项式的关系是:
伴随勒让德多项式可以由勒让德多项式求 m 次导得到:
等号右边的上标 (m) 表示求 m 次导。
公式中为band index,为degree,什么意思呢?伴随勒让德多项式分了很多个band,每个band里面又用作为index标注多项式,像这样:
其中要注意,定义域为[-1,1]以及只返回实数(勒让德多项式返回的是复数)
一般我们用下面三条规则来推导出所有伴随勒让德多项式,而不是直接一个个算。
1.
2.
3.
其中2可以算出第一项,3可以推出下一项,然后用1不断往下推导。
下面献上知乎专栏鸡哥的灵魂p图来帮助大家理解这三条递推规则。
另外,!!是双阶乘的意思,这是定义,不要自己乱来!
到此,我以为这就是whole story了。。。可惜,我还是太年轻了=_=!
事实上,以上所讲的伴随勒让德多项式依旧是一维的情况,要把定义在球面上的一个函数拆分成一组正交基底函数(我们把这组函数称之为球谐函数,下文都会用球谐函数表达),伴随勒让德多项式是核心,真正的公式是这样的:
其中就代表伴随勒让德多项式,这里多出来的是一个缩放系数
而和伴随勒让德多项式中的定义有所不同,这里m可以取负数,即:
有时候把球谐函数变成一维形式也挺有用的,所以我们定义
其中
原文中说大部分的论文都是只扔给读者一堆令人疑惑的多项式,但把公式转成图会更有趣,所以原文作者给了一张前5个band的可视化图:
是不是觉得这么一堆公式看着头都大了,遑论写代码了。Wikipedia
贴心的把前几个band的公式都解析出来了,照抄写死在代码都行!
还记得之前的公式以及求法么?(不记得回上去看下)就是上面所说的球谐函数,原函数通过随机在定义域上采样再通过蒙特卡洛积分得到,那么就可以得到这样一个公式
那么近似原函数即是:
以上公式中可以理解为采样点。那为什么最后是从累加到个参数呢?由于m取,那么伴随勒让德多项式就变成了
那么取一个band时()有1个多项式(即一个参数),取两个band时()有4个多项式(即4个参数),取三个band时()有9个多项式(即9个参数),以此类推,取n个band时则有个参数。
同样,原文作者也提供了一张可视化的图供大家加深印象
可以看出,n越大越能还原,即球谐函数band展开的越多,越能还原最初的信号。
上面所说的蒙特卡洛积分以及基底函数这些数学的东西其实已经可以还原原来的光信号了,然而。。。一看论文怎么才到一半左右,后面还有一半是什么鬼!?原来,接下来论文中阐述了一些球谐函数的特性,基于这些特性,我们认为球谐函数是优秀的!
1.标准正交性
这一特性表明对于球谐函数,使其变成一维(上文有说如何变成一维)则有:
2.旋转不变性
我们定义在球面上的原函数,将其旋转,变成另一个函数,那么,也就是说旋转函数本身的操作与将自变量旋转后交给是一样的,这个特性有什么用呢?原文的答案是我们在做动画、移动光源、或旋转物体的时候,光强不会产生涨落(fluctuate)、挪移(crawl)、抖动(pulse)等不良现象(objectionable artifacts)。
3.杀手级特性
不要惊讶,原文对于这个特性的表述就是the killer one。我们在做光照计算的时候,要考虑入射光的强度乘以表面反射项(这个东西称为传输函数(transfer function)),以此来获得最终的反射光。而这些都是在单位圆的表面做的,所以会有以下公式:
其中为入射光而为传输函数
如果我们把这两个函数做投影得到一堆系数时,那么球谐函数的标准正交性可以保证有:
即两个函数乘积的积分等于他们两个球谐系数的乘积的和
于是,我们一开始无法实时计算的这个东西就可以快速计算了。公式中这项是在描述入射光函数即,剩下的则是传输函数,分别用蒙特卡洛积分法近似得出原函数表达式后做投影,得出一堆后两两相乘再求和,结果就近似是原来那个难以积分的式子的结果。考虑到实际运行中(代表多少基底函数系数)一般都取3-6之间的数(也就是说最多是36,36对基底函数系数相乘再相加),计算量并不大,所以可以快速得出结果。
-
旋转球谐函数(Rotating Spherical Harmonics)
原文在这一小节里阐述了一下如何旋转那一堆我们得到的个参数,毕竟一旦物体旋转了(比如做动画),我们不可能去重新算一遍参数。原文这里看得我想死(数学太差T_T),大概就是构造了一个的旋转矩阵来把那个参数进行旋转,不用重新算。至于详细。。。看原文或者这里球谐光照与PRT学习笔记(四):球谐函数的性质与球谐旋转。
接下来,原文中讨论了好几种传输函数,我这边简单概括下。
1.无阴影漫反射传输(Diffuse Unshadowed Transfer)
回到最初给出的公式,把不必要的东西都剥离,只剩光源和某平面上受光的点,不考虑阴影,则:
其中:
= 点处沿方向的出射光 = 点处的BRDF
= 点处沿方向的入射光
=
由于漫反射的BRDF在每个方向都反射相同的光,即(与观察方向无关),那么公式中的可以忽略,它就变成了一个常量(原文称为),就可以提到积分外,公式就变为:
其中:
点处的表面反照率()
点处的法线
公式中的这个是怎么来的呢?我只想说这个不是瞎凑凑出来的,而是由数学推导出来的,详见如何看懂这些"该死的"图形学公式。
这时,传输函数就变为简单的,我们记为
2.有阴影漫反射传输(Shadowed Diffuse Transfer)
我们在这个公式基础上添加一个可见性项来获得自阴影,则
其中来自方向可见返回1,否则返回0
那么:
3.有相互反射的漫反射传输(Diffuse Interreflected Transfer)
这种方法最具有挑战性。方程最有意思的部分是它不仅将光源的直射光部分加进来,还将从其他可见点反射过来的光也考虑进来,原文中用了一个词是recursively,我的理解是由于光的反射不止一次,会考虑多次反射直至光被完全吸收或者超出视线范围,这么多次反射的光通过递归计算得到。于是:
其中:
有阴影漫反射传输(Shadowed Diffuse Transfer)所述公式
可见性测试
同一模型下由点反射向点的光,具体方向为
从数学上来讲,这个公式很难简洁的表述,但从算法角度却比较容易表述,总共分四步走:
- 对于模型上的每个着色点,计算这个点的直接光传输函数(例如:用 有阴影漫反射传输(Shadowed Diffuse Transfer)的公式计算)。
- 然后从现在的点发一条光线直到它遇到物体上面的另一个三角形,获得了一个撞击点,那么在三角形重心坐标体系下,对三角形每个顶点上的传输函数进行线性插值求出撞击点的传输函数,这个传输函数就包含了返射回点的光的信息。
- 将上一步获得的传输函数乘以然后累加起来,再用蒙特卡洛积分法算出点的传输函数参数。
- 一旦所有的点都算好了,这组新的参数就是一次反射所对应的参数,要获得N次反射所对应的参数,就用第一组参数作为开始设置不断做光线追踪,做到N次上限后停止。最后把直接光部分加进来就大功告成了。
几何上来讲这个点子很简单(呵呵。。。简单尼玛呢),模型上的每个点的直接光信息都是已知并以传输函数的形式进行编码,然后我们发射处=出一些射线来找到一些能反射过来光的点,把这些点的传输函数乘以再加到原来发出点的传输函数中,举个例子,上图点A发出一条射线,撞击点为B,那么B点的传输函数加到A是这样子的:
注意所有球谐函数都在同一坐标体系下,所以相加是种可行的操作。这样点A在被球谐光源所照射时,即时不能直接看见,也能被照亮。这个假设很重要的一点是,光照在整个模型上没有变化(例如点B处要和点A处有同样的光照函数)。这也是球谐光照的关键:低频光源以及物体上光源变化很小。
最终实现出来的效果如下:
最后,球谐漫反射表面的渲染通过那个杀手级特性计算,虽然上面说过了,不过原文还是把公式再列了一遍。
最最后论文写了一些照明与颜色标准的东西,这个由CIE(International Commission on Illumination,为什么缩写是CIE呢?原来这个组织的法语是 Commission internationale de l'éclairage,CIE是该组织法语的缩写=_=)组织定制,这些东西和球谐函数有关系,但更和Real Time Rendering那本书里的第八章有关系(我指的是第四版,第三版不是第八章,具体第几章我也不知道。。。),所以还是搁置到以后再说吧。
PS. 这个月就写了这么一篇博客,一则因为公司事多,二则因为这个月公司去了三亚年会旅游,搞掉一周,所以心思不在写博客上,希望下个月可以高产一点,多学点!
参考
简单理解spherical harmonic lighting(球谐光照)
球谐光照与PRT学习笔记(一):引入
球谐光照与PRT学习笔记(二):蒙特卡洛积分与球面上的均匀采样
球谐光照与PRT学习笔记(三):球谐函数
球谐光照与PRT学习笔记(四):球谐函数的性质与球谐旋转
球谐光照与PRT学习笔记(五):预计算传输与着色