1. 程式人生 > >我與SM不得不說的故事(一)

我與SM不得不說的故事(一)

話說當年學shader的目的, 就是為了想實現一個當時天真地以為很NB的演算法TSM, 一年多來朝著這個目標努力, 到最後卻發現, 結果並沒有想像中的美好-_-

最開始實現的是Projective Shadow, 也就是投影紋理的方法. 先把需要投影的物體從光源視角以單一顏色畫到一張RTT, 然後用投影矩陣生成紋理座標投影到地表上去跟紋理進行混合. 為什麼用它呢? 主要是它對硬體沒什麼要求, 能支援RTTOK. 那麼, 接下來, 有這麼幾個問題要解決:

l鋸齒: 沒辦法, 誰叫RTT那麼小呢, 再大也比地形要小

l陰影範圍: RTT就那麼大, 投影完了只是對應地上一小塊區域, 區域外的沒影子了

l陰影遮擋錯誤: 這個是投影陰影的硬傷, 它沒法判斷誰在前誰在後. 比如人站在橋上, 那麼不管橋墩, 橋面還是橋底下, 統統投上了影子. 你要是站在屋裡看到屋頂上有個影子, 這不是見鬼了麼...

l自陰影就不用想了, 會把自己的正反面全變成影子的顏色

那麼, 這些問題, 一個一個來. 找啊找啊找朋友, , 不對, 是找例子. 找到了NV SDK裡的PSM例子, 挺典型的, 大場景+平行光+四種演算法, 很不錯. 下面看看這幾個問題怎麼一個個地幹掉:

l鋸齒: 其實PSM系的演算法(包括LiPSM, TSM), 都是把View/Project矩陣拿來猥褻一番, 讓陰影在RTT佔的面積儘可能地大

. 變換完後, 離你近的陰影在RTT上比離你遠的要大. 所以, 這幾種演算法的核心是怎麼生成那個變換矩陣, shader那邊跟傳統的SM沒什麼差別. 所以, 那矩陣生成程式碼直接挖出來就能用@[email protected]

l陰影範圍: 這個是兩個因素決定的: 一是投影的物體所佔的範圍, 二是接收陰影的物體所佔的範圍. 跟據雙方的包圍盒加吧加吧就出一個範圍, 再根據這個範圍來對投影矩陣進行優化, 不浪費一塊地兒~ 事實證明, 這方法在不考慮接收陰影的物體時, 比什麼SM演算法都有效, 不過也會產生很詭異的問題. 比如, 你自己一個人時, 陰影精度高乎想像. 而人或NPC一多, 又變成原來那個樣子了

. 而且, 隨著NPC的走動, 陰影的鋸齒也在動, 抖啊抖得像是羊癲瘋

後倆錯誤, 沒有Depth Buffer是無藥可救了, 正好在NV這個例子裡注意到了它用的是自家的Hardware Shadow Mapping, 然後我就義無反顧地叛變了

HSM(但願這麼叫沒有跟某SM演算法衝突了), 有啥好處呢?

1.DepthBuffer時完全可以把ColorWrite關掉, 理論上比同時寫ColorBuffer+DepthBuffer要快2~4. 而且實現完了發現它可以跟ProjectiveShadowMapping(我又發明了個名字@#%[email protected])共用同樣的shader, 所以呢, 最後的實現結果會比它速度要快, 白來的效能不要白不要, 更何況遮擋錯誤也解決了.

2.根據NV官方文件的說法, HSM時開啟Linear過濾會免費給你進行2x2PCF模糊, 能夠減輕陰影邊緣的柔和度. 不過我當時用錯了, 後面提到PCF時再詳細說

壞處也有, 這是NV自己的標準! 但我用的ATi3600咋也能跑NV的這個DEMO? ATi官方只提到了它自己的那個什麼Fetch4技術, 同樣是DepthTexture, NV的難用多了. 反正這卡跑著沒問題, 當時就把ATi的那種實現直接無視了, 不支援就換成投影陰影好了. 後來在某年的顯示卡驅動新聞中知道, ATiHD2400之後的顯示卡也提供了對NVDepthStencilTexture的支援. 另外, N卡把DST當普通紋理時只能看到一片白, A卡確是紅黑相間, 可以把DST畫出來DEBUG陰影時用. 看看人家ATi, HSM的孃家都好.

下面說說自陰影的問題SM做自陰影簡單是對心理素質的一種磨練, 承受能力差的還是放棄吧-_-在與光線幾乎平行的面上的陰影交界處, 那是慘不忍睹啊….就算是地形這種平面, 要是做自陰影, 還有可能出現一大片的斑馬紋”~ 為什麼會這樣呢? 主要有兩個原因:

1.DepthBuffer精度不夠. 其實一般D24S8就夠了, 但是我同時又結合了TSM, 問題就來了. TSM變換完後Z的範圍都集中在一個很小的範圍內, 官方是建議把變換前的Z寫進buffer. 但是呢, 對於HSM, DepthBuffer裡寫什麼值不是我們能控制的(這個過程是跟固定管線混合的, 免去了shader的切換), 要想寫上自定義的Z, 需要自定義的VertexShader和浮點格式的RTT, 這個效能消耗就又上去了, 忽略. 還有就是ZNearZFar的控制, 這個結合上面說過的包圍盒裁剪就可以做得很好. 還有就是需要調節兩個bias值來減輕這種現象, 但是調多了也會產生走樣, 甚用~

2.再就是幾何問題了. 對於與光線近乎平行的面, 可能一大串的地方(從抽象機角度看)只對應深度紋理上一個在陰影裡的畫素, 那麼這一大串畫素就是黑的了(在陰影內). 正好旁邊RTT上的另一個畫素在陰影外, 那麼它對應的那一大串畫素就成白了的(陰影外) . 於是乎陰影交界處就產生了參差不齊的花紋, 從另一個角度看, 還挺有藝術感的. 這種情況, 調bias值一般解決不了. 所以, 大多數遊戲都不做自陰影的, 可憐地雞肋

對於1的問題, 調調bias, 弄弄Frustum, 調節一下CullMode估計就能解決的很好. 對於2, 我只知道一種方法: 模糊!

比如用SM來生成地形的LightMap, 有自陰影的話很難看, 但是做一下Blur之後, 就會發現, 原來效果這麼完美. 陰影邊界自然而然地有了過渡, 不在再生硬了, 走樣問題也被模糊掉了, 一舉兩得.

那麼實時陰影也是一個道理, 通常會採用PCF. 但是PCF的本意沒有錯, 錯的是它又帶來更嚴重的斑馬紋~關於它的解決方法, 還沒學習到. 暫且不說. 還有一種比較流行的SM : VSM, 是基於方差的, demo效果是不錯, 就是效率讓人受不了, 而且也沒法利用DST這個好東西了. 所以還是先看看怎麼來解決PCF的缺點來得實用.

今天先寫到這裡, 老婆召喚了~