天天看點

球諧光照——雜談——待完成

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

https://lianera.github.io/post/2016/sh-lighting-exp/

https://lianera.github.io/post/2017/sh-lighting-apply/ 源碼

球諧光照(spherical harmonics lighting)就是基于球面調和(SH,Spherical Harmonics)這個數學工具的一種着色算法。

一般來說,球諧光照可以用有限帶寬的Spherical Harmonics來模拟低頻的環境光照明,反射光和高光是高頻率,用低階球諧函數來編碼精度不夠。unity裡面的light probe使用的是3階SH來捕捉光照,是以我覺得light probe就有點像是使用spherical harmonics來編碼的光場(light field)捕捉器。

渲染方程

最簡單的光照模型之一就是漫反射模型(diffuse surface reflection model)了,它也被稱作是“點積光照”(dot product lighting),因為光照的強度是要乘個系數的,這系數就是表面法線

球諧光照——雜談——待完成

入射光向量

球諧光照——雜談——待完成

的點積,也就是:

球諧光照——雜談——待完成

這是渲染方程(The Rendering Equation)的簡化,而渲染方程是基于實體模組化推算出來的。考慮到一個點,由各個方向的入射光照亮,是以對上半球進行積分:

球諧光照——雜談——待完成
球諧光照——雜談——待完成

spherical harmonics 的harmonics一般譯作是調和,是以SH就叫球諧了。而調和函數(harmonic function)就是[3]拉普拉斯方程(Laplace’s Equation)

球諧光照——雜談——待完成

的解,解一般都含有sin和cos項,是以滿足拉普拉斯方程的函數就是harmonics的。在傅立葉分析裡面,我們有時會把機關圓上的周期函數(periodic function)用調和函數進行展開。把這操作推廣到n維球面,我們就會得到球面調和(Spherical Harmonics)。

很多論文都會直接把球諧函數的多項式扔給你,沒有可視化,非常抽象。上面的圖就給出了前5個band的球諧函數

球諧光照——雜談——待完成

的可視化結果。綠色是正值,紅色是負值,離中心越遠的地方絕對值就越大。這些基底是對稱的,這個對稱主要因為球諧波函數的對稱性(

球諧光照——雜談——待完成

球諧光照——雜談——待完成

)。

球諧光照——雜談——待完成

也就是要得到頻域上的系數Clm,就要通過求原函數f與Ylm的乘機上的球面上的積分。一般我們把這個求系數的過程叫做投影(projection)。在實際操作中,我們寫程式不可能會對無窮級數進行儲存和卷積的操作,一般展開項隻能是有限項,也就是:

球諧光照——雜談——待完成

其中n是球諧基band的數量,顯然n個band的球諧基數量是n平方個。這個過程是一個帶限的近似。大于一定門檻值的高頻信号就被去掉了。我們隻能用n平方個預計算的球諧系數(SH Coefficient)和球諧函數本身近似地重建出原函數:

球諧光照——雜談——待完成

從圖中可以看出,球諧展開階數越高,能重構出來的信号就越精确。

在實際操作中,球面積分一般也不太能直接求出解析解,需要使用monte-carlo積分近似求解積分。

蒙特卡洛積分法:

球諧光照——雜談——待完成

其中權重

球諧光照——雜談——待完成

那麼對于均勻的球面上采樣來說,權重就是:

球諧光照——雜談——待完成

是以球諧系數 (SH Coefficient)的數值估計表達式是:

球諧光照——雜談——待完成

在寫代碼實作的時候,用蒙特卡洛積分求系數的過程大概就是一系列的相乘與求和,僞代碼:

void SH_Coefficients()
{
    double weight =4.0 * PI;
    //生成n條光線進行采樣
    for(int i=0; i<n_samples; ++i) 
    {
   	 	//生成帶抖動的無偏采樣方向(θ,ϕ)
        for(int n=0; n<n_coeff; ++n)
        {
        	//對于某一個light probe,它的每個球諧展開系數c_i就要累加起所有的【某方向上的irradiance * 這個方向上SH函數值】
        	result[n] += light(θ,ϕ)* samples[i].SH_basis_coeff[n];
        }
    }
    // 把蒙特卡洛積分的常數項乘上去(恒定的采樣權重,總采樣數)
    double factor = weight / n_samples;
    for(i=0; i<n_coeff; ++i)
    {
        result[i] = result[i] * factor;
    }
}
           

上面的result[i]就是某個點上的球諧系數。我們可以用離線預計算的系數,在運作時快速近似重構原來的光照。

球諧光照——雜談——待完成

一般我們可以用SH coefficients來編碼低頻的環境光。

球諧函數的性質

标準正交性

球諧函數Ylm不隻是正交的,它還是标準正交的(orthonormal)的,作為一族基函數來說,它是優秀的。

球諧光照——雜談——待完成

旋轉不變性:

其次,球諧函數還是旋轉不變的(rotationally invariant)的。這個意思是,如果我們有旋轉操作R,那麼如果我們有一個定義在機關球面上的原函數f(s),設旋轉過後的函數是g(s),也就是:

球諧光照——雜談——待完成

那麼我們會有:

球諧光照——雜談——待完成

函數乘積的積分等于其球諧系數向量的點積

SH函數的這個特性是殺手锏級别的牛逼。在我們做光照的時候,通常情況下我們需要某種形式的入射光描述以及某種形式描述的表面反射(我們叫這個描述表面反射的函數叫傳輸函數(transfer function),可以是BRDF之類的reflection descriptor function)。把它們相乘,來得到結果的反射光。但是我們還是需要對整個半球面的入射光與傳輸函數的乘積進行積分。也就是我們要計算:

球諧光照——雜談——待完成

其中 [公式] 是入射光, [公式] 是傳輸函數。

球諧函數的旋轉(SH Rotation)

《Spherical Harmonics Lighting:the gritty details》原文其實并沒有給出很細緻的球諧旋轉相關的東西,就給你留下前3階的一些hardcoded過的SH旋轉什麼的。

旋轉一個球諧函數的動機:用預處烘焙看的球諧做ambient occlusion是可以顯著提高光照的效果的。到那時不少物體是需要動起來的(比如旋轉),每個頂點的SH都要跟着旋轉。那麼問題來了,我們不可能說物體旋轉了一下,我們得重新做SH Projection來得到旋轉過的函數SH。 是以需要一種可以直接操作SH space的系數的快速旋轉的方法。

更精确地描述,球諧函數旋轉的問題:給定一個球諧系數向量

球諧光照——雜談——待完成

,它表示的球面函數

球諧光照——雜談——待完成

。現在要找出另外一個球諧系數向量

球諧光照——雜談——待完成

來表示旋轉後的球面函數

球諧光照——雜談——待完成

,其中R是旋轉操作。

我們可以用要給n平方xn平方的旋轉矩陣來完成這一個操作。從SH函數的正交性出發,我們可以推出SH旋轉過程是一個線性操作,理論上旋轉後的函數對應的每個SH coefficient,都是可以表示為同一band裡面的SH coefficient的線性組合(是以才能用矩陣搞定),而且不同band之間的系數是不會互動的。而且這個矩陣理論上是稀疏的。

球諧光照——雜談——待完成

球諧光照實際上是一種對光照的簡化,對于空間上的一點,受到的光照在各個方向上是不同的,也即是各向異性,是以空間上一點如果要完全還原光照情況,那就需要記錄周圍球面上所有方向的光照。注意這裡考慮的是周圍環境往往是複雜的情況,而不是幾個簡單的光源,如果是那樣的話,直接用光源的光照模型求和就可以了。

如果環境光照可以用簡單函數表示,那自然直接求點周圍球面上的積分就可以了。但是通常光照不會那麼簡單,并且用函數表示光照不友善,是以經常用的方法是使用環境貼圖,比如像這樣的:

球諧光照——雜談——待完成

上面的圖是立方體展開得到的,這種貼圖也就是cubemap,需要注意的是一般的cubemap是從裡往外看的。

考慮一個簡單場景中的一個點,它周圍的各個方向上的環境光照就是上面的cubemap呈現的,假如我想知道整個點各個方向的光照情況,那麼就必須在cubemap對應的各個方向進行采樣。對于一個大的場景來說,每個位置點的環境光都有可能不同,如果把每個點的環境光貼圖儲存起來,并且每次擷取光照都從相應的貼圖裡面采樣,可想而知這樣的方法是非常昂貴的。

利用球諧函數就可以很好的解決這個問題,球諧函數的主要作用就是用簡單的系數表示複雜的球面函數。關于球諧函數的理論推導與解釋可以參考wiki。如果隻是要應用和實作球諧光照,不會涉及到推導過程,不過球諧基函數卻是關鍵的内容,球諧基函數已經有人在wiki上列好了表格,參考https://en.wikipedia.org/wiki/Table_of_spherical_harmonics

,前三階的球諧基函數如下:

球諧光照——雜談——待完成

這裡值得注意的是很多資料用這張圖來描述球諧基函數:

球諧光照——雜談——待完成

我剛開始看到這張圖的時候簡直覺得莫名其妙,實際上這裡面每個曲面都是用球坐标系表示的,球諧基都是定義在球坐标系上的函數,r(也就是離中心的距離)表示的就是這個球諧基在這個方向分量的重要程度。我是用類比傅裡葉變換的方法來了解的,其實球諧函數本身就是拉普拉斯變換在球坐标系下的表示,這裡的每個球諧基可以類比成傅裡葉變換中頻域的各個離散的頻率,各個球諧基乘以對應的系數就可以還原出原來的球面函數。一個複雜的波形可以用簡單的諧波和相應系數表示,同樣的,一個複雜的球面上的函數也可以用簡單的球諧基和相應的系數表示。

由于球諧基函數階數是無限的,是以隻能取前面幾組基來近似,一般在光照中大都取3階,也即9個球諧系數。

實驗:

我們先考慮簡單的情況,比如說定義一個光照函數:

球諧光照——雜談——待完成

在球坐标系下,将該函數的值當做光照強度值,可以畫出光照在球面上的分布情況:

球諧光照——雜談——待完成

不過由于這種方式可視化方式對于亮度變換不是很敏感,是以我們把強度當成球坐标系的r,畫出來是這個樣子:

球諧光照——雜談——待完成

現在要将這個函數轉換成球諧系數表示,首先要做的就是對其進行采樣,采樣的目标是确定在某個球諧基方向上強度的大小,也即求得每個球諧基Yi對應的系數Ci,具體的采樣方法如下:

球諧光照——雜談——待完成

其中N為采樣次數。也就是說在計算某個球諧系數Ci的時候,首先在球面上采許多點,然後把這些點的光照強度和球諧基相乘(在那個方向上,球諧基函數的分量或者說重要程度就是Yi(xi)),通過這些采樣點,進而得到了在每個球諧基函數上光照的分布情況。由于某個球諧基隻能大緻代表它那個方向上的光照強度,是以需要組很多個球諧基函數才能近似還原出原光照。

需要注意的是:采樣時必須要在球面上均勻采樣,如果在cubemap的每個圖像上逐像素采樣,将會導緻每個面邊角亮度提高,中心亮度降低。關于如何在球面上均勻采樣方法有很多,比如用正态分布随機生成x,y,z,然後歸一化成機關向量。

還原的過程比較簡單,通過球諧基與對應的系數相乘得到:

球諧光照——雜談——待完成

這裡L’是還原後的光照,s是球面上的一點(也可以看成某個方向),n是球諧函數的階數,n平方也即球諧系數的個數。

值得注意的是,采樣和計算Ci是預先進行的,比如說複雜場景中,某個位置預先用光線跟蹤方法計算環境光,進而采樣出ci,這樣這個位置的光照資訊就壓縮成了幾個Ci表示了。但是重建光照的過程是在運作時實時進行的。從重建光照的過程中可以看出該式子非常簡單,其中Yi的計算從球諧基函數的表中就可以看出隻涉及到簡單的乘法和加法,完全可以在shader中實作(球諧基函數中的r一般預設都設定成1)。是以,如果給我們一個點的球諧系數,利用上面的公式馬上就得到每個方向上的光照強度。

對于上面的那個光照函數來說,首先對原函數進行采樣,采樣1000個點并計算出前6階36個球諧系數,計算出的球諧系數(部分)如下:

球諧光照——雜談——待完成

計算好了球諧系數之後,我們就可以利用這些系數來還原原光照了,利用第二個公式還原之後的效果如下:

球諧光照——雜談——待完成

從左至右分别是原光照、02階球諧光照、05階球諧光照,從中可以看出到第5階球諧光照與原光照已經很接近了,隻是有小部分的高頻資訊不同。說明球諧系數越多,還原的效果越好,同時還原光照時能夠較好地保留低頻部分,而高頻資訊則丢失得比較多。不過對于光照來說,一般都是比較低頻的資訊,是以3階,也就是到l=2時就已經足夠了。

如果用CubeMap的方式來可視化就是這個樣子:

球諧光照——雜談——待完成

左圖為原環境光的CubeMap,右圖為0~5階球諧系數還原之後的光照,可以看出已經還原得很好了。

抛開簡單的函數,如果是複雜的環境光貼圖,過程也是一樣的,比如對于一個這樣的環境光:

球諧光照——雜談——待完成

對它進行采樣并還原之後,得到了這樣的結果:

球諧光照——雜談——待完成

效果還不錯,隻是高頻丢失了很多。不過這是對光照的還原,是以丢失了高頻資訊關系也不大。

如果把這兩個光照投射到球面上進行可視化,就是這個樣子:

球諧光照——雜談——待完成

球諧光照的應用:

我們知道,球諧光照實際上就是将周圍的環境光采樣成幾個系數,然後渲染的時候用這幾個次數來對光照進行還原,這種過程可以看做是對周圍環境光的簡化 ,進而簡化計算過程。因為如果按照采樣的方法進行渲染,每次渲染的時候都得對周圍環境采樣,進而都會耗費大量的計算時間。是以球諧光照的實作可以分成兩個部分,一是環境光貼圖的采樣和積分運算,生成球諧參數,二是利用球諧參數對模型進行渲染。

采樣器

采樣是從環境光上面采樣,而環境光我們可以用環境貼圖表示。環境光貼圖則可以采用cubemap的形式,也就是上面的十字狀的貼圖,不過這裡為了友善,把cubemap分成6個面,分布表示一個立方體的正x、負x、正y、負y、正z、負z。有了這六個貼圖,通過一種映射關系,我們就能知道空間中的一點周圍各個方向的光照值。具體的映射方法可以參考cubemap的wiki。

知道了每一個方向的光照值,要進行采樣,還需要計算出球諧基。球諧基實際上相當于某個方向上分量的多少,多個球諧基在不同的方向上分量不同,是以才能夠利用球諧基和球諧參數進行光照的還原。球諧基的計算方法,上一篇已經給出,例如,前四個分量的球諧基實際計算過程如下:

basis[0] = 1.f / 2.f * sqrt(1.f / PI);
basis[1] = sqrt(3.f / (4.f*PI))*y / r;
basis[2] = sqrt(3.f / (4.f*PI))*z / r;
basis[3] = sqrt(3.f / (4.f*PI))*x / r;
           

這樣,每采樣到一個像素,就計算相應的球諧基,并且對像素與對應的球諧基相乘後再求和,這樣就相當于每個球諧基在所有像素上的積分。不過,為了得到球諧基上的平均光照強度,還需要将積分得到的數值乘以立體角并且除以總像素。簡單說來就是運用這個公式求得球諧系數:

球諧光照——雜談——待完成
PBR

繼續閱讀