球谐函数#
就像泰勒展开的每个多项式前面的系数、傅里叶展开的每个三角函数前面的系数,球谐系数是对一组基函数的系数。
和前面的泰勒展开和傅里叶展开一样,可以把函数展开为用球谐函数表示。展开越高越逼近。
球谐函数长这样,很恐怖。它是在球坐标下的:

接下来将用 Ylm(x) 表示不同阶的球谐函数。
而对某个函数用球谐函数展开,长这样:
f(x)=l=0∑∞m=−l∑lclmYlm(x)其中 clm 就是球谐系数。
而球谐系数的算法和傅里叶展开系数算法类似,不过扩展到了二维。
clm=∫Ωf(x)Ylm(x)dx这是球面积分,是半球面。
球谐函数的性质#
球谐函数有很好的性质:正交完备性,旋转不变性。
正交完备性:

旋转不变性:
旋转不变性指原有一个输入 u,(θ,ϕ),用球谐函数表示的值 f(u),将这个输入旋转为 Rα,β,γu,新结果 f(Rα,β,γu) 可以直接通过原来的展开式获取,如下:

其中,dm′,ml是维格纳D矩阵,很复杂。
正交完备性使得它可以成为化简积分式的工具。即把积分成分用球谐函数展开,然后相乘展开,把交叉项去掉(积分为0)。
此外,作为不懂装懂的“资本”,球谐函数是球坐标下拉普拉斯方程的解:

球谐光照 - 视线相关光照#
用球谐函数表示光照。球谐函数本来就是二维上球坐标系上的,所以用来作为观察方向的映射到光照强度的方程非常方便,
球谐函数也常常用来表示视线无关的漫反射光照的,我也不知道我看的到底是以视线方向为输入求视线相关光照的,还是以每个点的位置为输入算视线无关漫反射光照的,当然两个肯定都能用球谐光照解决。下面是我自己看了点东西+瞎琢磨的。
因为是看 NeRF 相关文章的时候看到的,所以倾向于把球谐光照的光照方程理解为,对于每个体素(点),以观察方向为输入,得到这个体素在这个方向上的光照(颜色),这个延续了NeRF的思想。
那么每个点的光照方程就是: LS(v) 。每个体素都有这么个方程,在常见的场景(而非体渲染)里,这可能就是某个探针 probe 的光照表达,这是这个probe往周围某个方向散发出去的光照。关于 probe 我还没有看,这个理解应该是用问题的,姑且这么写着。
考察一些关于环境光照的问题,探针可能就是把位置无关的环境光照放进一个“球形纹理”里。渲染环境光照的时候,直接把方向作为坐标在球形纹理上取值即可。
这个光照方程用球谐函数近似,图形学里一般用二阶展开即可,一共 1+3+5=9 个系数。
LS(v)LS(v)=l=0∑2m=−l∑l(Clm)(S)Ylm(v)=i=0∑9(Ci)(S)Yi(v)球谐函数的值可以查表:

注意由于是在单位圆上表示方向(单位向量),所以上表示直接用r=1近似。注意这个表和我在百度百科上看的不太一样(百科上还有虚数 i在里面,不明)。
渲染之前,预先对每个点计算球谐系数,要计算球谐系数自然要把环境光照纹理上每个点的值算出来,这就是传统的渲染方程-光线追踪方法。这个计算中的球面积分用蒙特卡罗法即可(貌似均匀采样就行了),而采样中每个采样输入的光照的值用光线追踪等算法得到。
重建的时候,只用这个点的视线方向(或者发光方向)为(不同阶的球谐函数)的输入,与对应的球谐函数值(查表)相乘求和即可得到光照强度。
二阶球谐函数展开的情况下,rgb三个通道,每个9个系数,一共27个。
NeRF的加速版本的Plenoxel, PlenOctree只用到一阶,rgb每个通道4个系数共12个。
球谐光照与环境光#
利用正交不变性化简环境光方程#
Spherical Harmonic LightingGritty Details
解读
上面相当于只是用球谐系数表示了光照计算的结果,后面只用根据球谐系数就能得到光照。RGB三个通道每个通道需要9个球谐系数,一共需要27个球谐系数;一张纹理有三个通道,最后发现需要九张纹理表示所有球谐系数。太多了。
首先,场景中任意一点的漫反射光照:
L(p,n)=∫ΩL(p,wi)n⋅widwi其中,p是这个点的位置,n是这个点的法向量,wi 是球面上的其它方向。这同样是半球面积分。中间有个点乘,表示其它方向来的光与法向的作用。
把所有环境光当做是从天空盒射下来的,天空盒无限大,所以场景中任意一点都可以当做是天空盒的中心,所以结果就和位置p没什么关系,可以略去。这就是环境光照纹理的假设,环境光照是位置无关的;变为:
L(p,n)=L(n)=∫ΩL(wi)n⋅widwi这样,环境光光照就只和每个点的法线有关了。IBL 的思路就是,预先计算每个法向对应的 L(n)(此L(n)是光追后计算出的最终结果),形成一张贴图,在渲染的时候根据每个点的法向量找环境光照。此时环境光照只需要一张贴图。
但是贴图采样毕竟麻烦,有没有尽可能不需要贴图的方法?
上面这个式子可以用球谐函数的正交完备性化简。
将积分中划分为两个部分:L(wi) 和 t(n,wi)=n⋅wi.
分别展开:
L(w)t(n,w)=i=0∑LiYi(w)=i=0∑ti(n)Yi(w)Li,ti(n) 为球谐系数,后者右上角的 n 表示这个是法向n的球谐系数。
直接代回光照方程去:
L(n)L(n)L(n)=∫ΩL(wi)n⋅widwi=∫ΩL(wi)t(n,wi)dwi=∫Ω(l=0∑LlYl(wi))(l=0∑tl(n)Yl(wi))dwi展开两个括号,提出常数项,并用正交完备性 删去为0的项,得到:
L(n)L(n)=i=0∑j=0∑(Litj(n)∫ΩYi(w)Yj(w)dw)=i=0∑Liti(n)很好!这样L(n)就只用关注球谐系数了!
Li
Li=∫ΩL(w)Yi(w)dw由于对位置位于天空盒中心的假设,这里直接忽略p。L(w) 直接根据方向在天空盒上采样即可(这里的 L(w) 显然是没有光追的原始值;而渲染方程中的球积分里的 L ,应该是递归的光追结果,这里应该是存在一层近似!)。
ti^(n)^
这个系数很显然是和法向有关的。
ti(n)=∫Ωn⋅wYi(w)dw这又是一个半球面积分,并且这个法向 是甩不掉的。。这片论文里,不得不对每个法向 n 计算出了 ti^(n)^ 球谐系数(直接计算上面那个球积分),好在这里不存在三通道的问题(因为这只是和光线和法向的夹角有关,三通道在上面L那里,而L只需要算出Li九个系数即可),每个法向都要算这么九个系数,最后是需要 3 张存球谐系数的贴图(每个存3个).
虽然本文没有达到最开始的设想,但用球谐函数的正交完备性进行化简的思路非常有用。
利用旋转不变性解决上面的t系数问题#
An Efficient Representation for Irradiance Environment Maps
解读
因为旋转不变性比较复杂,这个更难。
CAUTION下面推导的东西大部分是我自己瞎琢磨,和原文无关。原文其实没太懂。故下面的内容是错的。但又不太想删。姑且放着。
思路是,只计算法向竖直向上的 t(n0,w) 的9个球谐系数 ti(0),这个不用贴图。任意法向都可以理解为从这个向上的法向旋转过去,然后利用旋转不变性的公式,从向上的球谐系数的展开式计算新法向的结果。
这个文章对 L(w) 同样用旋转不变性进行了一次推导,最后好像和上面的看起来不太一样。这里略去这部分,只考虑 t 。
对于竖直向上的 n0,有 t(n0,w)=∑i=0ti(0)Yi(w)
对任意方向 n=Rα,β,γn0,新的t(n0,w) 展开就是:
t(n,w)=t(Rα,β,γn0,w)=Rα,β,γn0⋅w=n0⋅R−α,−β,−γw=t(n0,R−α,−β,−γw)=l=0∑∞m=−l∑l(tlm)(0)Ylm(R−α,−β,−γw)而根据旋转不变性:
Ylm(R−α,−β,−γw)=m′=−l∑lDm′,ml(R−α,−β,−γ)Ylm′(w)由于原始法向在z轴,光线与法向的夹角只用考虑与z轴的夹角 α 即可,另外连个不管,索性赋值为0.
论文推导出,这种情况下,对于那个大D的项,有如下结论(这个结论不是无偏的,我也不知道什么意思):

可见最后直接和 α 无关了。这个结论是在原文推L(w)展开的时候推出来的,其中n显然也是个莫名其妙的法向,用在这感觉有问题..
Ylm(R−α,−β,−γw)=Ylm(n)m′=−l∑l2l+14πYlm′(w)代回去。
t(n,w)=l=0∑∞m=−l∑l(tlm)(0)Ylm(R−α,−β,−γw)=l=0∑∞m=−l∑l(tlm)(0)Ylm(n)m′=−l∑l2l+14πYlm′(w)=l=0∑∞m′=−l∑l2l+14πYlm′(w)(m=−l∑l(tlm)(0)Ylm(n))=l=0∑∞m=−l∑l2l+14πYlm(w)(m′=−l∑l(tlm′)(0)Ylm′(n))可得新的 ti(n)
tlm(n)=2l+14πm′=−l∑ltlm′(0)Ylm′(n)注意 tlm(n) 是和m无关的,只和 l 有关。
最后的渲染方程:
L(n)=l=0∑m=−l∑lLimtim(n)=l=0∑m=−l∑l2l+14πLlmm′=−l∑ltlm′(0)Ylm′(n)=l=0∑(m=−l∑l2l+14πLlm)(m′=−l∑ltlm′(0)Ylm′(n))
这个结果应是错的,和原文不符。原文结果是:

而 tl ,似乎是满足下面这样的关系的数列:

右边那个是什么规律,我也不知道。
中间推导过程、思路也完全不同。