接着上篇文章,我們實現了SSR效果。
其中的在屏幕空間進行光線追蹤的方法是通用的。借此我們再實現一種屏幕空間的效果,即屏幕空間陰影。
文中的圖片來自Catlike coding
http://catlikecoding.com/unity/tutorials/rendering/part-7/
完成的工程: https://github.com/yangrc1234/ScreenSpaceShadow
原生陰影
首先我們要了解一下原生的陰影是怎么實現的,這里我們只討論Directional Light。
首先,我們將Directional Light視作一個相機,對整個場景進行繪制。我們只需要其中的深度信息。(如果你嘗試過自己寫一個可以產生陰影的Shader,應該知道這個繪制是通過調用ShadowCaster類型的pass來實現的)
通過這樣渲染的一張ShadowMap,在我們渲染主相機的畫面時,對每一個像素,我們獲得它的世界坐標,然后將該世界坐標轉換到Directional Light的坐標系下,采樣對應的ShadowMap中的點。如果我們采樣出來的深度,大於該坐標的深度,我們認為該點沒有被阻擋。否則認為該點處於陰影中。
這就是ShadowMap方法的簡單描述。在Unity的Directional Light流程中,采用的是Cascade ShadowMap,此時會有若干個不同分辨率的ShadowMap被生成,分別對應與相機距離不同的區域,這樣可以做到相機較近的區域,分辨率較高,陰影質量更好;較遠的區域分辨率較低,質量一般(但是遠了你也看不出來)。
Cascade ShadowMap示例
不同於其他類型的光源,在主相機渲染時,我們求一個點的光照度,並不是直接去轉換坐標系然后采樣ShadowMap。在Directional Light流程中,在主相機渲染之前,Unity會將Cascade ShadowMap轉化為一張屏幕空間的陰影貼圖(Screen Space Mask,當然該過程也需要轉換坐標系去比較深度等等)。然后在主相機渲染時直接取采樣這張屏幕空間陰影貼圖獲得光照度。
屏幕空間的陰影貼圖示例
我們待會兒會通過操作這張屏幕空間陰影來實現我們的效果。
這種陰影實現毫無疑問是目前的主流方法。但是它也有不少問題,比如Shadow Acne現象。我們之前說到,判斷一個點是否在陰影中,是通過深度比較進行的。但是我們要判斷一個像素是否在陰影中時,因為深度貼圖的精度問題,可能會出現被周圍的差異極小的像素遮擋的情況。
屏幕空間陰影
屏幕空間陰影基於屏幕空間光線追蹤實現陰影效果。
最大的好處就是讓不參與ShadowMap繪制的物體也可以投出陰影。
而且作為一個屏幕效果,其效率與場景復雜度無關。
在繪制大量的小型物體時,這一優勢是很明顯的。
下圖是一個效果關閉開啟的對比。圖中的草是Unity的Terrain系統繪制的,默認不開啟陰影。可以看到開啟屏幕空間陰影后畫面提升明顯。