【游戲開發】游戲中的平面陰影怎么做
光和影是渲染領域最核心的兩個需求,伴隨著實時圖形學的發展數十年,學界和工業界的相關研究也層出不窮,要綜述這個話題實在是個大活兒,所以我還是從Shadow Map和Ray-Traced Shadow出發,圍繞這兩種時下最流行的方案對相關算法做一個梳理。
從最簡單的陰影開始
陰影是表示空間位置關系的重要線索,拋開其視覺質量本身不談,有和沒有陰影能夠提供的視覺信息量是完全不同的。哪怕只是在角色腳下渲染一個半透明圓盤,也能夠很大程度上改善游戲的3D體驗,很多早期的游戲里確實就是這么干的。
相比小圓盤更復雜一點的陰影技術叫做平面陰影(Planar Shadow),它基于一個簡單的觀察:物體在平面上的投影還是能夠清晰地保持自身的輪廓,就好像模型被壓扁在平面上了。
(1)先繪制平面,再關閉深度測試和寫入進行平面陰影的繪制,最后再打開深度的測試和寫入進行模型的正常繪制。
(2)在平面投影矩陣中,稍稍減小(或者增大,取決于法線的方向)一點d的值,讓實際的投影平面位于模型平面上方。
第二個問題是半透明疊加。早期游戲在繪制陰影的時候,為了模擬全局照明的貢獻,通常不會把陰影繪制為純黑,而是帶一定半透明的黑色。于是當三角面被壓扁在投影平面上時,會產生重疊,這時如果開啟半透明混合,不論使用什么樣的疊加模式最后都會出現錯誤的半透明效果,因為實際上我們只希望平面上最多覆蓋一個像素的半透明陰影。解決方法是首先繪制下方的模型平面,并向Stencil Buffer寫入一個指定值(比如1),然后開啟模板測試,比較函數設為EQUAL,測試通過后模板值 1[2],這樣如果某個點已經繪制過一次半透明陰影,則相同位置就不會再繪制第二次。
平面陰影技術至今也還活躍在很多手游中,對于機能有限的平臺仍然是很好的選擇。它不存在陰影走樣的問題,繪制性能也很高,同時也有一些缺陷,最嚴重的當然是影子只能投射在平面上,對于曲面上的投影就無能為力了;其次,如果光源恰好位于被投影物體和平面之間,理論上是不產生投影的,但是實際上它會在平面上投射出錯誤的陰影來;此外,平面投影算法無法模擬軟陰影。
Shadow Volume
陰影體(Shadow Volume)[3]也是個比較古老的技術,雖然現在已經鮮有游戲使用,但是算法本身還是相當精巧。Shadow Volume名氣最大的應用當屬傳奇大佬John Camack開發的《DOOM3》,算法思路見下圖:
這個算法解釋起來稍微有點復雜。首先,我們從光源位置出發,針對陰影遮擋體的每個三角面生成一系列半開的棱臺,這些棱臺被我們稱之為陰影體(Shadow Volume),所有位于陰影體內部的點,都會被遮擋而看不到光源,也就是需要繪制陰影的位置。那么如何判斷一個點是否位于陰影體內部呢?這正是算法的關鍵所在:假設我們從攝像機向屏幕上任一點發射一條線段,當線段和陰影體正面相交時,則表示它進入了一個陰影體,當線段和陰影體背面相交時,則表示它離開了一個陰影體,于是,我們每進入一個陰影體,就讓交點數 1,反之離開一個陰影體,就讓交點數-1,如果最終所有交點數(Shadow Volume Count)為0,則表示當前點位于陰影體之外,若交點數大于0,則表示當前點位于陰影體內。于是判斷一個點是否位于陰影體內的問題就變成了判斷Shadow Volume Count是否大于0。
具體實現:
(1)打開ZWrite/ZTest,關閉Stencil Write/Test,繪制一遍場景(只繪制非投影光源的貢獻)
(2)關閉ZWrite,ZTest保持Less Equal,打開Stencil Write,將其設置為 1,Stencil Test設為總是通過,繪制投影光源的陰影體正面
(3)將Stencil Write設置為-1,繪制投影光源的陰影體背面
(4)將ZTest設置為Equal,關閉Stencil Write,Stencil Test設置為Equal,再次繪制場景中Stencil Value為0的區域(疊加模式為ADD,繪制非陰影區域,且本次只繪制投影光源的貢獻)
上述算法基于一個假設:攝像機本身是位于陰影體外的,但實際情況往往并非如此。于是John Carmark在原始算法的基礎上進行了改進,提出了ZFail算法。不同于ZPass的算法,這次線段發射的起點位于距離攝像機無限遠處(我們可以肯定這個點一定是位于陰影體外的),然后我們每次遇到陰影體背面,就讓Stencil Value 1,遇到陰影體正面,就讓Stencil Value-1,其他設定均不變。
Shadow Volume的算法能夠生成精確的硬陰影,但也有一些明顯的缺點,比如難以實現軟陰影效果,此外場景的多遍繪制加上陰影體的生成和繪制,使得算法對場景幾何復雜度非常敏感。
Shadow Map
Shadow Map[4]是目前主流的陰影生成算法,這主要得益于它算法直觀,并且能夠充分利用現代硬件的光柵化能力。
標準的Shadow Map算法思路很簡單,也是很多人入門圖形學的基礎算法之一:對于指定光源來說,場景中某個點是否被其照亮,取決于從光源的視角看去,這個點是否可見。假設該點可見,則表示沒有遮擋,反之則表示該點處于陰影中。于是,算法被分成了四步:
從光源視角生成Shadow Map
將光源投影空間的深度映射到屏幕空間
深度比較,確定陰影區域
由于光柵化導致的精度誤差,通常直接比較深度會產生些許誤差,產生所謂的“Shadow Acne”,通常的解決方案是在比較前給加上一個固定偏移,但是若偏移選取過大,又會產生所謂的“Peter-Panning”的問題,一個自適應偏移的方案,是基于斜率去計算當前深度要加的偏移(Slope Scale Depth Bias)。
由于相機和光源視角不同,從光源視角光柵化后的每個像素投影到屏幕空間后,對應的區域大小也不相同,所以往往會出現距離相機較近位置的Shadow Map精度不夠,而距離相機較遠位置的Shadow Map精度又過高的問題,于是就會出現陰影邊緣的明顯鋸齒。
緩解這個問題的方法是把視錐沿著Z軸切分成多段,每段單獨計算出一個光源坐標空間內的緊湊AABB,然后基于這個AABB生成多張Shadow Map,也就是所謂的級聯式陰影(Cascaded Shadow Map)[5]。在進行深度查詢時,首先根據當前像素在相機空間中的Z值確定其位于哪個分段中,然后找到對應分段的Shadow Map和投影矩陣。
在實際操作中,通常會選擇3~4級分段,劃分位置通常是指數劃分和均勻劃分的結果進行插值后得到。鑒于劃分是基于視錐的,所以較遠處的Shadow Map可以預先計算好[6],或者每隔幾幀才更新一次,以此提高渲染效率。
相比標準Shadow Map,級聯式Shadow Map的像素利用率提高了
視錐劃分算法
提高Shadow Map像素利用率的另一個方案是設法獲得更加緊湊的視錐包圍盒。由于相機在場景中始終處于變化狀態,因此整個屏幕空間中可見像素的包圍盒也在變化,且這個包圍盒往往要比相機默認的視椎體緊湊許多,假設我們能夠通過場景的ZBuffer去統計得到這個包圍盒,再結合CSM去做場景劃分,就可以最大限度的避免Shadow Map中像素的浪費,這就是Sample Distribution Shadow Map(SDSM)[7]的核心思想。
基于PSSM劃分的場景層級分布
基于SDSM劃分的場景層級分布
PSSM和SDSM視錐劃分投影到光源空間后的面積
PCF方法看起來是美好的,但也存在一個致命的問題:它需要大量的采樣,例如一個3x3的Kernel需要9次采樣,5x5的Kernel則需要25次采樣,隨著采樣半徑增大,這個數字會迅速增加。這時候我們會自然地想到:不是可以把一個2D濾波拆分成兩個獨立的1D濾波再利用硬件的雙線性采樣來減少采樣次數嗎[12]?這個思路確實是對的,然而實際卻不可行,因為我們的可見性判定函數是一個二值函數。進一步來說,
VSM的可見性判定函數
當然VSM也有一個顯著的缺陷,觀察切比雪夫不等式就可以發現,當Shadow Map某個區域內深度變化劇烈,導致 很大的時候,這個可見性估計就不再準確了,表現在視覺上,就是原本應該完全處于陰影的區域出現了漏光。
ESM由于貼圖數值精度不足導致的錯誤
鑒于VSM和ESM各自的優缺點,也有人提出了兩者結合的方案,即EVSM(Exponential Variance Shadow Maps)[15],這里不再詳細展開。
以上都是通過設計一個能夠預先濾波的可見性判定函數去改進Shadow Map的算法思路,類似的方案還有CSM(Convolution Shadow Maps)[16],也有一些綜述類文檔對比了它們各自的特性[17],這里我們不再贅述。
完結!!!
轉載聲明:本文來源于網絡,不作任何商業用途

全部評論


暫無留言,趕緊搶占沙發
熱門資訊

第18屆王座杯CG大賽獲獎名單公布!

參與王座杯后,同學們直呼太幸福!免費吃肯德基方法get!...

網飛再出沙雕番!沙雕又賢惠的黑道大哥竟是我老公之《主夫的誘惑》?...

適合30歲女人的職業有哪些?

王座杯獲獎采訪集合

CG人物女性盔甲人物角色作品欣賞

米哈游:新作《崩壞:星穹鐵道》,預計登錄移動和PC市場...

28歲沒學歷應該去學什么技術?

數字媒體應用技術專業學什么?
