Unity5.X中屏幕空間陰影投射技術(Screenspace ShadowMap)如何產生陰影圖?
最近在讀&
,其中Page197(9.4節提到),使用Screenspace ShadowMap技術後,Unity會根據相機的深度圖和ShadowMap的深度作比較來生成陰影圖,我的理解是,相機的深度圖,是從遊戲玩家的視角產生的,而ShadowMap是從光源位置觀察得到的,這兩個圖的坐標系不一樣,去比較有意義么?
我是作者,既然是讀者提的問題還是要回答一下。書里在P197下面是這麼寫的:「如果攝像機的深度圖中記錄的表面深度大於**轉換到**陰影映射紋理中的深度值,就說明……處於該光源的陰影中」,所以意思就和上面幾位答主的意思一樣,要先轉換到同一個空間里再去比較深度值。
然後看到你的提問後,我就在github的issue【常見問題】對9.4節Unity的陰影的補充說明(重要) · Issue #49 · candycat1992/Unity_Shaders_Book里又補充了下,就直接粘貼過來啦
--------------------------------今天知乎上有人問書裡面提到的屏幕空間陰影技術是怎麼實現的。這個書上在原理實現上講得很簡略,不過大家只要有耐心都可以在Unity里找到它的實現。這種屏幕空間陰影的實現是延遲渲染裡面陰影的常見實現方法,網上也有一些文章介紹它們,例如這篇Tutorial - Deferred Rendering Shadow Mapping。
屏幕空間的陰影延遲渲染中的光照計算絕大部分都是在屏幕空間里進行的,同樣也包括陰影。這種屏幕空間的陰影實現主要有這麼幾個步驟:
- 首先得到從當前攝像機處觀察到的深度紋理。在延遲渲染里這張深度圖本來就有,如果是前向渲染的話就需要把場景整個渲染一遍,把深度渲染到深度圖中。
- 然後再從光源出發得到從該光源處觀察到的深度紋理,也被稱為這個光源的ShadowMap。
- 然後在屏幕空間做一次陰影收集計算(Shadows Collector),這次計算會得到一張屏幕空間陰影紋理,也就是說這張圖裡面需要有陰影的部分已經顯示在圖上了。這個過程概括來說就是把每一個像素根據它在攝像機深度紋理中的深度值得到世界空間坐標,再把它的坐標從世界空間轉換到光源空間中,和光源的ShadowMap裡面的深度值對比,如果大於ShadowMap中的深度距離,那麼就說明光源無法照到,在陰影內。
- 最後,在正常渲染物體為它計算陰影的時候,只需要按照當前處理的fragment在屏幕空間中的位置對步驟3得到的屏幕空間陰影圖採樣就可以了。
上面這個過程在9.4.3節大致提到過。這裡再補充一些實現細節問題,其實這個過程就和三張紋理的生成有關係:攝像機的深度紋理,光源的ShadowMap,以及靠前兩者得到的屏幕空間陰影紋理。下面主要還是針對Unity裡面的實現大概解釋一下,希望有興趣的還是要自己去trace下Unity的各個文件看看它的實現。
下面就是在Frame Debugger里看到的結果:
攝像機的深度紋理和光源的ShadowMap這兩張紋理是前期的準備工作。在Unity里在是前向渲染路徑的情況下,這兩張紋理主要都是靠有一個Shader中LightMode為ShadowCaster的Pass來完成的,這個實現細節在這個issue上面的答案中給了非常詳細的解釋。不再贅述。
上面那張ShadowMap有很多空白區域是因為開啟了4層的Shadow Cascades,所以實際上渲染了四張ShadowMap。由於這個場景的FarPlane值比較大,而物體離攝像機都很近(距離在10以內),所以其他三張就啥也沒渲染到。
屏幕空間陰影紋理這張紋理也是靠內置的一個Shader渲染得到的。從Unity 5.4的Frame Debugger里看到的這個Pass的名稱是Hidden/Internal-ScreenSpaceShadows(在DefaultResourcesExtra/Internal-ScreenSpaceShadows.shader)。這個Pass在不同的Unity版本里是不一樣的,比如在Unity 5.3裡面就是Internal-PrePassCollectShadows(在DefaultResources/Internal-PrePassCollectShadows.shader),看的時候還是要全局搜索確定下。
這個Shader挺長的就不放了,主要思路就是從攝像機的深度紋理里採樣得到該fragment的深度值,然後利用矩陣變換計算得到該點對應的世界空間的世界坐標(利用CameraToWorld矩陣),然後再變換到光源空間下的坐標(利用WorldToShadow矩陣),最後拿這個坐標對光源的ShadowMap採樣計算陰影。
物體的陰影在前向渲染裡面渲染每個物體的時候,會先計算fragment在屏幕空間的位置scrPos,然後再據此對屏幕空間陰影紋理採樣即可。這個在Unity裡面就是靠內置宏來完成的,比如SHADOW_COORDS、TRANSFER_SHADOW、UNITY_LIGHT_ATTENUATION那一套,這個也可以自己看到實現代碼的。
需要換算到統一坐標系
根據相機的深度圖能計算到世界坐標,進而去算到光源方向的裁剪空間下的深度,用此深度與之前渲染的ShadowMap比較即可。
推薦閱讀: