遊戲中的陰影實現
陰影對於提高遊戲真實感非常總要,簡單總結下游戲中的陰影實現.
先來看下陰影的組成部分
陰影的組成部分
1.平面對映
最簡單的情況就是物體在一個平面上投射陰影,這種情況下只是需要通過矩陣把產生陰影點面投射到平面上.
從v點對映到p點:
令
推導後寫成矩陣的形式:
如果receiver不是一個無窮大的平面,需要通過stencil buffer標記出需要接受陰影的部分.
同樣需要注意避免這種情況,這時候不應該繪製出陰影.
右邊的情形下不應該繪製出陰影
如果想要Soft Shadow的效果,可以在光源周圍使用多個點進行投射陰影,然後取出一個平均值,但是這樣產生的陰影效能消耗很大,一般需要幾百次取樣平均才能得到較好的效果.
另外一種方法是先得到陰影貼圖,然後做一個blur處理.這樣比較快,缺點就是不符合實際陰影近處清晰遠處模糊的特點.
2.Projector實現
和上面的方法非常類似,區別在於先把陰影從光源的視角先渲染occluder得到陰影,再投射到receiver上,這樣就可以把陰影作用到不平坦的面上.
這兩種方法的缺點就是需要明確地知道occluder和receiver,只能適用於簡單的場景.但是效能消耗相對較小,所以在手游上仍然能看到這些方法的應用.
甚至在手游上可以直接將陰影做成一張固定的貼圖,以decal的形式貼到地面上,雖然是很簡單的形式,也能極大地增強真實感.
3.Shadow Volumes <參考GPU Gems 3 Chapter11>
一種非常過時的渲染陰影的方法,但是其思想很值得學習借鑑.
1.shadow volume:
就是從光源沿著模型邊緣拉伸至無限遠處加上前蓋後蓋形成的形狀.
shadow volume
2. z-pass演算法:
shadow volume陰影的思想就是取一條從視點到目標點的線,每次進入shdow volume,計數加一,每次離開計數減一,這樣計數為0的部分就是無陰影的地方.
通常使用stencil buffer來實現,從視點渲染shadow volume集合體,開啟z-test,正面部分+1,背面部分-1,stencil buffer為0的部分就是無陰影部分.
z-pass
3. z-fail演算法:
z-pass演算法有個缺陷,當視點在shadow volume中的時候,會產生錯誤的結果.
所以就有了z-fail的演算法,z-fail演算法其實就是從物體背面計數,在z-test fail的幾何體部分,在進入shdow volume時計數-1,離開時計數+1,這樣就可以規避這個缺陷.
z-fail
不過z-fail演算法普遍要比z-pass要慢,因為從背面渲染shadow volume,通常會覆蓋更多的畫素點,其次從上圖可以看出,使用z-fail時必須渲染shadow volume的capping部分(前蓋後蓋).因此在渲染前可以做一個攝影機是否在shdow volume中的簡單判斷,來決定使用z-pass或者z-fail演算法.
4. 生成陰影體的步驟:
一種最常見的一種生成shadow volume的方法,不過這種方法要求目標模型是封閉的多邊形網格(沒有空洞,裂隙,自相交).
分為三部分: front capping 前蓋-> back capping 後蓋-> silhouette 輪廓拉伸成的側面
front capping就是取模型中面向光源的三角面,方向判斷可以通過判斷面法線和光源方向的乘積的正負值來判斷.
back capping 取模型中背向光源的面,沿光源方向拉伸到無窮遠處.
silhouette 判斷兩個臨接面與光源方向不同的邊,認為是輪廓邊,將每條邊擴充套件拉伸到無窮遠處形成一個四邊形面.
5. 在無窮遠出的渲染:
如何表示無窮遠處的點?使用齊次座標將w分量置為0,xyz表示方向即可.
如何避免圖元在攝影機far clip plane外被裁剪掉?
一種方法是使用 GL_DEPTH_CLAMP_NV 擴充套件, 將far plane外的點clamp到裁剪空間中.不過這個方法好像是隻適用於OpenGL 和 NVIDIA顯示卡.
另外一種方法是稍微修改下攝影機的裁剪矩陣,將far plane設定為無窮遠
普通攝影機矩陣
變成下面這樣:
遠裁面在無窮遠處的攝影機矩陣
當然精度或有微乎其微的減少.
6. 適用於非封閉模型的方法:
把模型分成兩部分,一部分是面向光源的面,一部分是背向光源的面,分別進行拉伸生成shadow volume,就可以支援非封閉模型.缺點是原來的輪廓邊相當於生成了兩次,造成效能浪費.
左邊是面向光源面,右邊是背向光源面,兩個加在一起形成正確的結果
7. 使用Geometry shader生成Shadow volume
使用GS可以將生成shadow volume的工作移交給GPU,不過必須用TRIANGLE_STRIP的方式來輸入模型.
使用GL_TRINGLES_ADJACENCY_EXT模式來向GS中輸入三角形圖元,就可以獲取三角形的鄰接面,以此在GS中進行輪廓邊判斷,輸出Shdow volume等操作.
Geometry Shader中輸入的頂點
4. Shadow Map
當下應用最廣泛最常見的方法,從光源處出發向光照的方向渲染需要產生陰影的物體,得到儲存最近處物體的深度值的shdow map.
對於directional light使用一個足夠大的orthographic projection包住所有需要渲染的物體, spot light使用一個和自己光照範圍相當的frustrum, omini light沿六個方向生成類似於 cubic environment mapping的 omnidirectional shadow maps.
渲染物體光照時,將畫素點代入到光照的矩陣中,和shadow map中該點的深度值比較,如果深度值大於shadow map中深度值,說明該點在陰影中.
因為Shadow map的解析度限制,可能會出現 self-shadowing,因此需要加上一個小的bias偏移量.
self-shadowing
5. Shadow Map 增強
1.Cascaded Shadow Maps(CSM)
參考文獻
通常在渲染視角附近的物體時需要更高的shadow map精度,而直接生成的shadow map往往不符合這個條件,所以將frustum分割成數個部分,提高視角附近shadow map的精度.
CSM
Unity中的CSM,不同的顏色代表不同的CSM區域
2. Percentage-Closer Filtering (PCF)
參考文獻
在取樣點附近周圍選取一些點,分別進行depth-test,將測試結果進行平均.
現在的硬體大多提供周圍四點取樣的加權PCF深度測試(OpenGL中的sampler2DShadow, DirectX中的 SampleCmp)
再向外的PCF就需要手動偏移取樣點,簡單的方法是使用N*N的Grid方式取樣,高階的方法是在一個disk中用預計算好的Possion分佈(或者其他帶抖動的分佈方式)的點取樣,然後每個畫素取樣時對disk進行旋轉,產生soft shadow的效果.
從左到右是 4*4 Grid取樣, 12點Possion取樣, 12點Possion取樣 + 旋轉, Possion分佈圖
3. Percentage-Closer Soft Shadows (PCSS)
參考文獻
根據光源到目標點距離和Occluder到光源距離,計算PCF的軟陰影程度值,再進行PCF處理,得到近處銳利遠處模糊的陰影.
PCSS的陰影效果
4. Linearized Depth
參考文獻
普通的陰影可能會出現在遠處精度不足的情況,因為一般的陰影深度z值不是線性的,在近處精度大,遠處精度小.所以有線性陰影的做法.
改變普通的FS程式碼,大致寫成這樣:
- float4 vPos = mul(Input.Pos,worldViewProj); vPos.z = vPos.z * vPos.w / Far; Output.Pos = vPos;
先將深度值除以Far遠平面的值,得到0-1的線性陰影深度值,再乘以w值,這樣在光柵化時得到深度值vPos.z / vPos.w,自然是我們得到的0-1的線性陰影深度值.
5. Variance Shadow Map(VSM)
參考文獻
用PCF產生軟陰影時,每計算一次深度值,需要取樣很多個點的深度進行比較然後求和.
VSM是一種Filtered Shadow Map,可以對Shadow map進行blur或者mipmap,每次計算陰影時,不需要取樣目標點周圍的很多個點,節省效能.
生成兩張Shadow Map,一張是普通的深度值,另外一張儲存深度值的平方.
對兩張Shadow map進行blur,每個新的畫素點的值是原來點周圍點值的加權平均.
兩張ShadowMap 中值的含義,可以得到方差值
如果求得的目標點的深度值小於ShadowMap中的 E(x)值,認為該點被完全點亮,不渲染陰影,這裡和普通的陰影渲染一樣.
當目標點深度值大於E(x)值時,根據Chebyshev不等式推匯出該點周圍點深度值大於目標點深度值的概率:
根據這個概率值,就可以來計算軟陰影的程度.
6. Exponential Shadow Map ESM
參考文獻
ESM也是一種類似VSM的Filtered Shadow Map
假設d代表shadow map中的深度值,z代表目標點的深度值,得到陰影函式f(d, z) = 0 (當d > z) / 1 ( 當d<=z),f(d,z)叫做Heaviside step function.
ESM就是用一個指數函式來模擬f(d,z):
圖中可以看出指數函式和Heaviside step function很相近,而且c值越大,近似效果越好.
Shadow Map中儲存exp(c*d)的值,可以進行blur,來產生軟陰影.
普通的ESM會有精度限制的問題,會限制c的值不能太大,所以有改進版的ESM,具體比較可以參考這個切換到ESM - KlayGE遊戲引擎以及上面的參考連結.
7. Pack To RGBA
某些移動平臺不支援浮點數紋理,需要將shadow map的深度值pack到RGBA貼圖中
- //Pack:
- vec4 comp = fract(depth * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0));
- comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);
- //UnPack:
- float depth = dot(texture((m_tex), (m_uv)), vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0))
也有使用256替換255的版本,但是用255比256是要好的.在某些硬體上用256會有表現不一致情況,而且精度略低些.
作者:TC130專欄地址:https://zhuanlan.zhihu.com/p/104687855
相關文章
- 假陰影,低開銷的陰影實現方式
- 陰影進階,實現更加的立體的陰影效果!
- Android開發中陰影效果的實現Android
- CSS 陰影進階,實現更加的立體的陰影效果!CSS
- WPF 實現陰影效果
- 遊戲裡的動態陰影-ShadowMap實現原理遊戲
- 利用著色器在WPF中實現陰影特效特效
- css實現邊框陰影效果CSS
- HTML中的盒子陰影HTML
- 僅用 CSS 實現多彩、智慧的陰影CSS
- Android Material Design 陰影實現AndroidMaterial Design
- 實現給一個DIV加陰影效果!
- 使用純 CSS 實現滾動陰影效果CSS
- css圖片陰影、文字陰影CSS
- 短視訊系統,實現介面陰影效果
- 《Morkredd》:使用陰影構建遊戲玩法遊戲
- CSS實現帶陰影的三角形CSS
- 小Tip:有三角的指示框陰影實現
- css實現帶有陰影的立體文字效果CSS
- CSS3實現的文字框陰影發光效果CSSS3
- CSS3實現的div陰影效果程式碼例項CSSS3
- css3實現的文字陰影效果程式碼例項CSSS3
- boder 陰影
- 【譯】徹底理解 Android 中的陰影Android
- css去除ios中input和textarea的陰影CSSiOS
- 寶可夢被禁、遊戲開發者判死刑:陰影中生長的伊朗遊戲遊戲開發
- 在 iOS 裡 100% 還原 Sketch 實現的陰影效果iOS
- css3實現的邊框陰影效果程式碼例項CSSS3
- css實現動態陰影、蝕刻文字、漸變文字CSS
- WebGL實踐之半透陰影Web
- 灰色產業的陰影中,CDKey成了罪魁禍首產業
- css3實現div邊框陰影效果程式碼例項CSSS3
- 簡單陰影分析
- 【CSS】曲線陰影CSS
- view邊框陰影View
- Shadow Map(陰影貼圖)跟Soft Shadows(軟陰影)
- view的陰影效果shadowColorView
- 遊戲市場陰影下的遊戲手機廠商,和他們無法觸碰的未來遊戲