體積陰影(Shadow Volumes)產生演算法

來源:互聯網
上載者:User

標籤:style   blog   http   color   java   使用   os   io   

 

體積陰影(Shadow Volumes)產生演算法 分類: opengl 2012-08-13 13:42 811人閱讀 評論(0) 收藏 舉報 演算法bufferlist測試blendshader是3D初心者:) 不是技術百科全書,在blog上想說什麼就說什麼:) 下面以最快的速度簡單談談陰影產生技術,目前普遍採用的一般有三種:Planar Shadow、Shadow Mapping和Shadow Volume,前者類似投影,計算最簡單,缺點只能繪製拋射在平面上的陰影;Shadow mapping利用站在光源處所沿光源法線看去所產生的深度圖來檢測情境中的體象素是否處於陰影中,缺點是光源與物體位置相對固定、且在極端情況下計算精度差,不太適合精確到象素的動態光陰場合;Shadow Volume是目前最適合精確表現動態光陰情境的技術,適用性最廣,其典型的適用範例便是Doom 3,不足在於陰影體積引入了額外的頂點和面,加大了儲存和處理強度,同時渲染出的陰影比較硬,如果要實現軟陰影,仍需其他技術配合。

這裡我們快速往前跳,Perspective projection、Depth test、Stencil buffer等概念就不多談了。Shadow Volume的一般步驟為:產生陰影體積(Mesh)和陰影渲染,陰影體積產生演算法又分兩種,一種是D3D SDK sample中所採用的方法,先分離/插補物體面,然後用Vertex Shader加速,對複製所得的未展開的陰影體積進行沿光線方向的Extrusion;另一種則是多數tutorial中常用的Software Renderer方式,首先揀選出所有向光面,將每一個向光面的所有邊均加入一個list,每加入一個邊時,如果list中已經存在相同的邊了,則不加入,同時將list中相同的邊刪除,這樣處理完所有的向光面後,list中便只會剩下物體向光面輪廓的邊資訊,將該輪廓沿光向量方向進行延伸,然後完成側面Quad插補和前後封口(如果使用的是Z-fail)即可。而對於陰影渲染,則一般用4個pass,具體有Z-Pass/Z-Fail兩種做法:

Z-pass演算法
1. 先關閉光源,將整個scence渲染一遍,此時一片漆黑,但獲得了深度值
2. 關閉深度寫,渲染陰影體的正面,深度測試通過則模板值加1
3. 然後渲染陰影體的背面,深度測試通過則模板值減1
4. 最後模板值不為0的面就在陰影體中,開啟深度寫
5. 用模板手法重新渲染一次加光的scence即可,讓陰影部分為黑色
6. 其致命缺點是當視點在陰影中時,會導致模板計數錯誤
7. 同時,有可能因為Z-near clip plane過近而導致模板計數錯誤

Z-fail演算法(John Carmack‘s Reverse)
1. 先關閉光源,將整個scence渲染一遍,獲得深度值
2. 關閉深度寫,渲染陰影體的背面,深度測試失敗則模板值加1
3. 渲染陰影體的正面,深度測試失敗則模板值減1
4. 最後模板值不為0的面便處於陰影體中,開啟深度寫
5. 用模板手法重新渲染一次加光的scence即可,陰影部分不渲染色度
6. 注意,該演算法要求陰影體積是閉合的,即需要前後封口
7. 該方法不是沒有缺陷的,有可能因為Z-far clip plane過近而導致模板計數錯誤

值得注意的方面
1. Z-pass由於不用封口,因此速度比Z-fail快,但存在處理不了的情況
2. Quake 3貌似使用的是z-pass shadow volume和planar shadow
3. 為保證足夠robust,必須確保z-near/z-far中至少一個不出問題,Nvidia的論文推薦採用z-fail,用w=0來實現無窮遠的z-far平面
4. 記住使用Z-fail一定要封口,而且陰影體積的每個面的法線必須正確地指向物體之外,包括front cap和back cap
5. 上面給出的是簡單過程,陰影很硬,可以稍微變通一下:先以環境光線渲染一遍,然後計算模板,再用模板渲染打光的scene,最後以alpha blend加深陰影

下面開始大面積地貼圖,首先來看個Z-fail的具體例子(z-pass簡單,就不舉例了):從外面看一個面向光源的單面的情況。圖中,虛線框起來的為陰影體積,圓形為點光源,其中,面ADFB和面DEF是back faces,而面ABC、ACED和CBFE則是三個front faces(注意這裡頂點順序用的是左手系),為說明問題(主要是為了說明在計算陰影體積時如何避免z-fighting),我故意把面ABC畫在了黃色三角形下面一點點的位置,而且其中的灰色陰影是與平面共面的,而面DEF則處於平面下面一點點的地方。



首先進行2個back faces的depth test:粉紅色的是stencil buffer中因Z- fail而加1的地區;然後進行3個front faces的depth test:亮綠色的是stencil buffer中因Z- fail而減1的地區



最後紅綠地區正負相抵,stencil buffer中只剩下平面上的灰色三角地區中的值不為0,即原圖陰影所在的位置。上面是個從單面外看的例子,值得注意的是,單面與兩個背靠背雙面的情況是不一樣的,那麼後者的計算結果是不是也是正常的呢?繼續看圖:



的紅、綠細線分別表示兩個背靠背單面,而粗線則構成了體積陰影,紅粗線、綠粗線分別表示紅面、綠面沿光線方向的的投影,一般而言,back cap處於無窮遠處,而front cap則值得注意,為避免z-fighting,它的位置應該是在紅面之後、綠面之前,也許你會說:“hey, 這裡的front cap完全可以直接等於紅面嘛”,是的,當在eye看來紅面是背面時的確可以這麼做,可當eye位置變化、紅面不再是背面時,簡單的以紅面為front cap便會導致z-fighting。從圖上可看出,當eye處於陰影內部時,模板計數也是正確的。下面再來看看僅有一個單面、且處於陰影內部的情況:



只要front cap注意了z-fighting的情況,其計算結果也是正確的,有意思的是,實際上當eye處於陰影內部時,在back culling的時候,三角形的背面便已經被去除了,因此在實際渲染時,在三角面的背面處並不會存在其深度資訊,但即便如此,z-fail的模板計數仍是正確的。



最後,再來看一個計算方塊的陰影的例子,可以看出,當front cap為背面時,陰影體積的計算很簡單,無需考慮其z-fighting的情況,直接用方塊的向光面作為front cap即可;但當front cap為正面時,還是面臨著位置需要微調的問題。

總結一下,用z-fail方式來計算沒有厚度的面的陰影和具有厚度的物體的陰影還是有點微妙區別的;在計算陰影體積時,要注意避免front cap處z-fighting的情況,稍微調整一下front cap的位置,即將front cap沿光線方向稍微向後挪那麼一點點;或者是.... 等等,還有一種更簡潔優雅的辦法,即將Z-fail演算法中的"Depth Test失敗"理解成象素深度">="所在位置的深度而不僅僅只是">",將ZBufferFunction設為Campare.Less而不是Campare.LessEqual,這樣的話便完全避開了z-fighting的問題,微調什麼的都可以免了,無論是面還是物體,在任何情況下都可以直接用物體向光面作為front cap....faint...快寫完了才考慮清楚...白廢話了那麼多....無比鬱悶-_-b....收工.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.