球谐光照入门简介

在我的上一篇博客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)。

既然是一种光照算法,那么论文一开始就是从一个光照公式开始谈起的:
L(X, \vec\omega_o) = L_e(X, \vec\omega_o) + \int_sf_r(X, \vec\omega_i\rightarrow\vec\omega_o)L(X', \vec\omega_i)G(X,X')V(X,X'){\rm d}\omega_i
其中:
L(X, \vec\omega_o) = \omega_o方向的光照在点X处所反射出来的光的强度
L_e(X, \vec\omega_o) = 点X处的自发光
f_r(X, \vec\omega_i\rightarrow\vec\omega_o) = 点X处的BRDF(Bidirectional Reflectance Distribution Function)
L(X', \vec\omega_i) = 从另一个物体上的点X’传递过来对点X有影响的光,并且这道光的入射角度为\omega_i
G(X,X') = XX’之间的几何关系,一般就是\vec N \cdot \vec L,也就是cosine \ term
V(X,X') = 可见性测试,X能看见X’就返回1,否则返回0

对于f_r(X, \vec\omega_i\rightarrow\vec\omega_o)L(X', \vec\omega_i)G(X,X')V(X,X')这四项进行实时积分是很困难而且昂贵的,所以我们需要一些数学工具来简化这一积分计算,变不可能为可能!

蒙特卡洛积分
首先我们来看第一个数学工具,蒙特卡洛积分。公式是这样子的:
\int f(x){\rm d}x = \int \frac {f(x)}{p(x)} p(x) {\rm d}x \approx \frac {1}{N} \sum^N_{i=1}\frac {f(x)}{p(x)}

这个东西的核心思想就是对于一个连续函数f(x)在其定义域随机挑选N个值x_i(这个操作用个词概括就叫采样)并求出N个f(x_i),除以每个值被挑选中的概率p(x_i),将他们累加起来除以N,就近似得到了原函数f(x)的积分结果。那么对于这个式子而言,采样越多则结果越逼近真实的积分结果
不过由于这个积分是个球面积分,定义域是在球面上,要在定义域上进行均匀的采样我们还需要一个公式:
(2arccos(\sqrt{1-\xi_x}),2\pi\xi_y) \rightarrow (\theta,\psi)

其中\xi_x\xi_y是0-1之间的均匀随机变量,他们俩是独立的。\theta,\psi是采样方向,在球面上可以理解为维度经度。


用上面这个公式就可以均匀随机的在球面上进行采样,

我们知道单位球面积为{4\pi},那么随机采样一个点的概率为\frac{1}{4\pi}

再利用蒙特卡洛积分来近似计算结果。如果有朋友对这个公式怎么来的有兴趣,可以看看Generating uniformly distributed numbers on a sphere这篇博客。

基底函数
信号处理中有个叫傅里叶变换的东西(我没学过信号处理,没法专业的解释这东西,需要专业解释还是请教谷歌吧),简单来说就是对于任意一个函数f(x),可以把它拆成一组三角函数乘以系数之后的和,即F(x) = \sum c * f(x)。这里的f(x)为一组正交基底函数,函数正交意味着有两个函数f(x)g(x) \int f(x)g(x){\rm d}x = 0
我们有了公式F(x) = \sum c * f(x)后,就要开始找出cf(x)f(x)是一组正交基底函数,古往今来已经有很多前辈探索发现了许多的正交基底函数,我们在这里特别关注的是一个叫做伴随勒让德多项式的东西,不过首先我们先把这个放一边,认为f(x)是已知的,那么现在唯一要求的是cc的求法是在F(x)上采样一堆x绘制出F(x)的曲线,乘以f(x)再积分(积分相当于把F(x)投影到f(x)上来求f(x)F(x)的贡献度),得到的值就是我们要求的c。这一过程在原文中被称为投影projection),所以本篇博客中所有出现投影一词时都是这个意思。现在c有了f(x)有了,我们就可以大致求出原函数F(x),过程如下图。



只要我们采样足够多,选定的基底函数足够多,计算出来的函数就越接近原函数。另外基底函数会通过权重进行排序,一般排在前面的基底函数比较重要,也称为低频部分,它为定下了基调,通常第一个基底函数模拟的是一个平均值,越往后面的基底函数重要性越低,被称为高频部分,展现了的一些噪声部分。

现在可以来看f(x)了。之前我们看到了傅里叶变换可以把任意一个函数F(x)拆分成一组正交基底函数,F(x)是基于x轴的。而我们之前看到的渲染函数却是基于球面的,基于球面的函数一般表达为F(\theta,\psi),两个参数是单位球在两个轴的夹角。在球面上也有类似于傅里叶变换的东西,叫勒让德多项式,特别的,我们会关注一个叫伴随勒让德多项式的东西。根据Wikipedia,伴随勒让德多项式与勒让德多项式的关系是:

伴随勒让德多项式可以由勒让德多项式求 m 次导得到:
P^m_l(x) = (1-x^2)^{m/2}P^{(m)}_l(x)
等号右边的上标 (m) 表示求 m 次导。

公式中l为band index,m为degree,什么意思呢?伴随勒让德多项式分了很多个band,每个band里面又用m作为index标注多项式,像这样:
P^0_0(x)
P^0_1(x)P^1_1(x)
P^0_2(x)P^1_2(x)P^2_2(x)
\ldots\ldots\ldots\ldots
其中要注意m \in [0,l], l \in R^+,定义域为[-1,1]以及只返回实数(勒让德多项式返回的是复数)
一般我们用下面三条规则来推导出所有伴随勒让德多项式,而不是直接一个个算。
1.(l-m)P^m_l = x(2l-l)P^m_{l-1} - (l+m-1)P^m_{l-2}
2.P^m_m = (-1)^m(2m-1)!!(1-x^2)^{m/2}
3.P^m_{m+1} = x(2m+1)P^m_m
其中2可以算出第一项,3可以推出下一项,然后用1不断往下推导。
下面献上知乎专栏鸡哥的灵魂p图来帮助大家理解这三条递推规则。


另外,!!是双阶乘的意思,这是定义,不要自己乱来!
到此,我以为这就是whole story了。。。可惜,我还是太年轻了=_=!
事实上,以上所讲的伴随勒让德多项式依旧是一维的情况,要把定义在球面上的一个函数拆分成一组正交基底函数(我们把这组函数称之为球谐函数,下文都会用球谐函数表达),伴随勒让德多项式是核心,真正的公式是这样的:

y^m_l(\theta,\psi) = \begin{cases} \sqrt{2}K^m_lcos(m\psi)P^m_l(cos\theta), & m>0 \\ \sqrt{2}K^m_lsin(-m\psi)P^{-m}_l(cos\theta), & m<0 \\ K^0_lP^0_l(cos\theta), & m=0 \\ \end{cases}

其中P就代表伴随勒让德多项式,这里多出来的K是一个缩放系数
K^m_l = \sqrt{\frac{2l+1}{4\pi} \cdot \frac{(l-|m|)!}{(l+|m|)!}}

m和伴随勒让德多项式中的定义有所不同,这里m可以取负数,即:l \in R^+, -l\leq m\leq l

有时候把球谐函数变成一维形式也挺有用的,所以我们定义y_i
y^m_l(\theta,\psi) = y_i(\theta,\psi) 其中i=l(l+1)+m

原文中说大部分的论文都是只扔给读者一堆令人疑惑的多项式,但把公式转成图会更有趣,所以原文作者给了一张前5个band的可视化图:


是不是觉得这么一堆公式看着头都大了,遑论写代码了。Wikipedia
贴心的把前几个band的公式都解析出来了,照抄写死在代码都行!

还记得之前的公式F(x) = \sum c * f(x)以及c求法么?(不记得回上去看下)f(x)就是上面所说的球谐函数,原函数F(x)通过随机在定义域上采样再通过蒙特卡洛积分得到,那么c就可以得到这样一个公式
c^m_l=\int_sf(s)y^m_l(s){\rm d}s
那么近似原函数\tilde{f(x)}即是:
\tilde{f(x)} = \sum^{n-1}_{l=0}\sum^l_{m=-l}c^m_ly^m_l(s)=\sum^{n^2}_{i=0}c_i y_i(s)
以上公式中s可以理解为采样点。那为什么最后是从0累加到n^2个参数呢?由于m取[-l,l],那么伴随勒让德多项式就变成了
P^0_0(x)
P^{-1}_1(x)P^0_1(x)P^1_1(x)
P^{-1}_2(x)P^{-2}_2(x)P^0_2(x)P^1_2(x)P^2_2(x)
\ldots\ldots\ldots\ldots
那么取一个band时(l=0)有1个多项式(即一个参数c),取两个band时(l=1)有4个多项式(即4个参数c),取三个band时(l=2)有9个多项式(即9个参数c),以此类推,取n个band时则有n^2个参数c
同样,原文作者也提供了一张可视化的图供大家加深印象


可以看出,n越大越能还原,即球谐函数band展开的越多,越能还原最初的信号。

上面所说的蒙特卡洛积分以及基底函数这些数学的东西其实已经可以还原原来的光信号了,然而。。。一看论文怎么才到一半左右,后面还有一半是什么鬼!?原来,接下来论文中阐述了一些球谐函数的特性,基于这些特性,我们认为球谐函数是优秀的!

1.标准正交性
这一特性表明对于球谐函数,使其变成一维y_i(上文有说如何变成一维)则有:
\begin{cases} \int y_iy_j=1 & i=j \\ \int y_iy_j = 0 & i\not =j \\ \end{cases}

2.旋转不变性
我们定义在球面上的原函数f(s),将其旋转,变成另一个函数g(s),那么g(s) = R(f(s)),也就是说旋转函数本身的操作与将自变量旋转后交给f(s)是一样的,这个特性有什么用呢?原文的答案是我们在做动画、移动光源、或旋转物体的时候,光强不会产生涨落fluctuate)、挪移crawl)、抖动pulse)等不良现象objectionable artifacts)。

3.杀手级特性
不要惊讶,原文对于这个特性的表述就是the killer one。我们在做光照计算的时候,要考虑入射光的强度乘以表面反射项(这个东西称为传输函数transfer function)),以此来获得最终的反射光。而这些都是在单位圆的表面做的,所以会有以下公式:
\int _sL(s)t(s){\rm d}s
其中L为入射光而t为传输函数
如果我们把这两个函数做投影得到一堆系数c时,那么球谐函数的标准正交性可以保证有:
\int_s\tilde L(s)\tilde t(s){\rm d}s = \sum^{n^2}_{i=0}L_it_i
两个函数乘积的积分等于他们两个球谐系数的乘积的和
于是,我们一开始无法实时计算的\int_sf_r(X, \vec\omega_i\rightarrow\vec\omega_o)L(X', \vec\omega_i)G(X,X')V(X,X'){\rm d}\omega_i这个东西就可以快速计算了。公式中L(X', \vec\omega_i)这项是在描述入射光函数即\tilde L(s),剩下的则是传输函数\tilde t(s),分别用蒙特卡洛积分法近似得出原函数表达式后做投影,得出一堆c后两两相乘再求和,结果就近似是原来那个难以积分的式子的结果。考虑到实际运行中n(代表多少基底函数系数)一般都取3-6之间的数(也就是说n^2最多是36,36对基底函数系数相乘再相加),计算量并不大,所以可以快速得出结果。

  1. 旋转球谐函数Rotating Spherical Harmonics
    原文在这一小节里阐述了一下如何旋转那一堆我们得到的n^2个参数,毕竟一旦物体旋转了(比如做动画),我们不可能去重新算一遍参数。原文这里看得我想死(数学太差T_T),大概就是构造了一个n^2\times n^2的旋转矩阵来把那n^2个参数进行旋转,不用重新算。至于详细。。。看原文或者这里球谐光照与PRT学习笔记(四):球谐函数的性质与球谐旋转

接下来,原文中讨论了好几种传输函数,我这边简单概括下。
1.无阴影漫反射传输Diffuse Unshadowed Transfer

回到最初给出的公式,把不必要的东西都剥离,只剩光源和某平面上受光的点,不考虑阴影,则:
L(x,\omega_o) = \int_sf_r(x,\omega_o,\omega_i)L_i(x,\omega_i)H(x,\omega_i){\rm d}\omega_i
其中:
L(x,\omega_o) = 点x处沿\omega_o方向的出射光\int_sf_r(x,\omega_o,\omega_i) = 点x处的BRDF
L_i(x,\omega_i) = 点x处沿\omega_i方向的入射光
H(x,\omega_i) = cosine \ term

由于漫反射的BRDF在每个方向都反射相同的光,即view \ independent(与观察方向无关),那么公式中的\omega_o可以忽略,它就变成了一个常量(原文称为constant \ scalar),就可以提到积分外,公式就变为:
L_{DU}(x) = \frac{\rho_x}{\pi}\int_\Omega L_i(x,\omega_i)max(N_x \cdot \omega_i){\rm d}\omega_i
其中:
\rho_x =x处的表面反照率(surface \ albedo
N_x =x处的法线

公式中的这个\pi是怎么来的呢?我只想说这个不是瞎凑凑出来的,而是由数学推导出来的,详见如何看懂这些"该死的"图形学公式

这时,传输函数就变为简单的cosine \ term,我们记为M_{DU}
M_{DU} = max(N_x \cdot \omega_i)

Diffuse Unshadowed Transfer效果图

2.有阴影漫反射传输Shadowed Diffuse Transfer

光有可能被挡到的情况

我们在L_{DU}(x) = \frac{\rho_x}{\pi}\int_\Omega L_i(x,\omega_i)max(N_x \cdot \omega_i){\rm d}\omega_i这个公式基础上添加一个可见性项V(\omega_i)来获得自阴影,则
L_{DU}(x) = \frac{\rho_x}{\pi}\int_\Omega L_i(x,\omega_i)V(\omega_i)max(N_x \cdot \omega_i){\rm d}\omega_i
其中来自\omega_i方向可见返回1,否则返回0
那么:
M_{SD} = V(\omega_i)max(N\cdot \omega_i,0)

Shadowed Diffuse Transfer效果图

3.有相互反射的漫反射传输Diffuse Interreflected Transfer
这种方法最具有挑战性。方程最有意思的部分是它不仅将光源的直射光部分加进来,还将从其他可见点反射过来的光也考虑进来,原文中用了一个词是recursively,我的理解是由于光的反射不止一次,会考虑多次反射直至光被完全吸收或者超出视线范围,这么多次反射的光通过递归计算得到。于是:
L_{DI}(x) = L_{DS}(x) + \frac{\rho_x}{\pi}\int_\Omega \overline L(x',\omega_i)(1-V(\omega_i))max(N\cdot \omega_i){\rm d}\omega_i
其中:
L_{DS}(x) = 有阴影漫反射传输Shadowed Diffuse Transfer)所述公式
V(\omega_i) = 可见性测试
\overline L(x',\omega_i) = 同一模型下由点x'反射向点x的光,具体方向为\omega_i
从数学上来讲,这个公式很难简洁的表述,但从算法角度却比较容易表述,总共分四步走:

  1. 对于模型上的每个着色点x,计算这个点的直接光传输函数(例如:用 有阴影漫反射传输Shadowed Diffuse Transfer)的公式计算)。
  2. 然后从现在的点发一条光线直到它遇到物体上面的另一个三角形,获得了一个撞击点,那么在三角形重心坐标体系下,对三角形每个顶点上的传输函数进行线性插值求出撞击点的传输函数,这个传输函数就包含了返射回x点的光的信息。
  3. 将上一步获得的传输函数乘以cosine \ term然后累加起来,再用蒙特卡洛积分法算出点x的传输函数参数。
  4. 一旦所有的点都算好了,这组新的参数就是一次反射所对应的参数,要获得N次反射所对应的参数,就用第一组参数作为开始设置不断做光线追踪,做到N次上限后停止。最后把直接光部分加进来就大功告成了。
    示意图

    几何上来讲这个点子很简单(呵呵。。。简单尼玛呢),模型上的每个点的直接光信息都是已知并以传输函数的形式进行编码,然后我们发射处=出一些射线来找到一些能反射过来光的点,把这些点的传输函数乘以cosine \ term再加到原来发出点的传输函数中,举个例子,上图点A发出一条射线,撞击点为B,那么B点的传输函数加到A是这样子的:

    注意所有球谐函数都在同一坐标体系下,所以相加是种可行的操作。这样点A在被球谐光源所照射时,即时不能直接看见,也能被照亮。这个假设很重要的一点是,光照在整个模型上没有变化(例如点B处要和点A处有同样的光照函数)。这也是球谐光照的关键:低频光源以及物体上光源变化很小
    最终实现出来的效果如下:

最后,球谐漫反射表面的渲染通过那个杀手级特性计算,虽然上面说过了,不过原文还是把公式再列了一遍。
\int_s\tilde L(s)\tilde t(s){\rm d}s = \sum^{n^2}_{i=0}L_it_i = L_0t_0 + L_1t_1+L_2t_2+L_3t_3\ldots

最最后论文写了一些照明与颜色标准的东西,这个由CIE(International Commission on Illumination,为什么缩写是CIE呢?原来这个组织的法语是 Commission internationale de l'éclairage,CIE是该组织法语的缩写=_=)组织定制,这些东西和球谐函数有关系,但更和Real Time Rendering那本书里的第八章有关系(我指的是第四版,第三版不是第八章,具体第几章我也不知道。。。),所以还是搁置到以后再说吧。

PS. 这个月就写了这么一篇博客,一则因为公司事多,二则因为这个月公司去了三亚年会旅游,搞掉一周,所以心思不在写博客上,希望下个月可以高产一点,多学点!

参考
简单理解spherical harmonic lighting(球谐光照)
球谐光照与PRT学习笔记(一):引入
球谐光照与PRT学习笔记(二):蒙特卡洛积分与球面上的均匀采样
球谐光照与PRT学习笔记(三):球谐函数
球谐光照与PRT学习笔记(四):球谐函数的性质与球谐旋转
球谐光照与PRT学习笔记(五):预计算传输与着色

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容