首先貼一個鏈接,該鏈接內有大量基於OpenGL的渲染技術教程和Code Sample,本文基本上在其Tutorial 40的基礎上進行了翻譯,並加入了部分自己的理解。原文在此:
http://ogldev.atspace.co.uk/index.html
Shadow Volume,即陰影體技術。是CG中非常常見的陰影渲染技術。在自己動手實現之前,也看了好多原理上的東西。但是紙上得來終覺淺,絕知此事要躬行啊! 以此文記錄下Shadow Volume實現過程中的種種。
簡單地講,光線照射空間幾何物體,被物體遮擋住的空間沒有不能受到光源光線的直射,這個空間同樣可以用一個幾何體表示,這個幾何體就是所謂的Shadow Volume(以下簡稱SV),即陰影體。位於SV里的物體的即為被陰影包圍的物體。
實現時的一些關鍵點:
1、SV的生成。
2、Z-Fail Stencil Test.
3、陰影的渲染。
1、SV的生成:
如上圖所示,左圖的綠色部分和右圖的灰色部分即為 陰影體。陰影體是實實在在的Mesh,跟左圖的”人“和右圖的”摩托車"一樣,都是由三角形面片組成的。我們需要根據光源的位置和陰影的生產者(Shadow Caster)的形狀去生成這個Mesh(某物體A遮住了光源,產生了陰影,我們稱物體A為Shadow Caster,簡稱SC。某物體B被物體A遮擋,在B上造成了陰影,我們稱物體B為Shadow Receiver,簡稱SR)。仔細觀察上圖,我們會發現陰影體起始於SC面向光源的面,而終止於SR。陰影體在SC上的面像個蓋子(cap),在SR上的面像底兒(bottom),蓋子和底兒之間的三角形面片圍一圈形成邊兒(surrounding),這個密閉空間就是陰影體的空間。用一個簡單的三角形作為SC,如下圖所示:
綠色的Cap(即三角形ABC),深灰的bottom(即三角形A'C'B')以及surroundings(四邊形CBB'C', CC'A'A, AA'B'B)組成了SV Mesh。!注意上述三角形的順序,遵循右手定則。
但是實際上,陰影體的Cap並不是位於SC上,而是沿着光源的方向有一個小的偏移量。陰影體的Bottom也不在SR上,而在無窮遠處,上圖中的A',B',C'分別為A,B,C沿着入射光方向被投射到無窮遠處的對應點。如下圖所示:
紫色的三角形A''B''C''是真正的Cap。這是為了避免Z-fighting。
那么,生成陰影體Mesh的問題可以轉化為尋找蓋子,底兒和邊兒的過程。有了這些面兒,將它們圍起來就是一個閉合的陰影體Mesh。
下面開始生成SV Mesh。對於SC的一個三角形面片T,它有三個邊a,b,c,三個鄰面Ta,Tb,Tc,分別與T共享邊a,b,c。那么偽代碼如下:
1 for every triangle facet T in the SC: 2 3 if T faces the light 4 5 generate the cap triangle by applying a displacement to the original T. //生成Cap 6 7 generate the bottom triangle by projecting the original T to infinity. //生成Bottom 8 9 for every adjacent triangle Tx ( x = a,b,c ) 10 11 if Tx faces the light 12 13 edge x is not part of the silhouette, continue. 14 15 else 16 17 edge x is part of the silhouette, generate the surrouding triangles. 18 19 else 20 21 continue
上面的偽代碼在Geometry Shader中實現。
上面的偽代碼中提到了 Silhouette (輪廓)。是的,SV Mesh 的 Surrounding Triangles 只在屬於 Silhouette 的三角形邊處生成。關於silhouette如何檢測,上述偽代碼已經說得比較清楚了,其原理也比較簡單,但是Silhouette detection在大多數情況下仍然作為一個單獨的問題來討論。
下面就簡單提一下:
silhouette Detection(邊界檢測)。邊界檢測相關資料很多了,原理也很簡單。在計算機圖形學中,物體由三角形面片(triangle facet)組成。以光照邊界檢測為例,物體外部有某光源(point,spot or direction light ),這個物體上的所有三角面片要么直接受該光源照射(該面法向量與入射光線方向夾角大於90小於180°),要么不直接受光源照射(該面法向量與入射光線方向夾角大於等於0°小於等於90°),那么必然有一個“邊的集合”位於這兩種三角形面片相交的邊界處。邊緣檢測即找出這個“邊的集合”。如下圖二維示意圖所示。面向和背向光源的三角面片共享的邊即我們需要尋找的邊。(該圖摘自http://ogldev.atspace.co.uk/www/tutorial39/tutorial39.html ,在原圖基礎上加入中文圖示)
如何去尋找呢?很簡單。如下圖所示,對某三角形A,有鄰面B,C,D。如果A面對光源,那么遍歷其三個鄰面,如果鄰面背對光源,那么A與該鄰面共享的那條邊即是邊界。如果其三個鄰面也全都面對光源,那么面A的三條邊都不屬於邊界。
該邊緣檢測的計算過程可以在Geometry shader中進行。由於Geometry shader 可以訪問Primitive的adjacency信息,所以可以方便地對每個三角形的鄰面進行信息讀取和計算。當然,在OpenGL Application端需要向Pipeline提供具有adjacency信息的數據並按照一定地規則進行排列。例如上圖中的三角形A,在提供此Primitive信息的時候應該按頂點索引(0,1,2,3,4,5)的順序在內存中排列。具體如何從任意3d模型中計算adjacency信息,在此不詳細說明。
如何將SC上的頂點project到無窮遠處作為Bottom的頂點呢?如下圖所示:
光源將頂點P投射到無窮遠處,然后投影到near clip(近裁剪面)的上,其X軸坐標為Xndc.
(圖引自 http://ogldev.atspace.co.uk/www/tutorial40/tutorial40.html )
n為近裁剪面的距離,v為光源到頂點P的向量,t為標量,從0到正無窮。當t趨向正無窮時,就得到我們想求的Xndc。
(圖引自 http://ogldev.atspace.co.uk/www/tutorial40/tutorial40.html )
同理,Yndc也可由此得到。
至此,SV的生成到此結束。
下圖是實驗結果:五環上方有旋轉的點光源,下方是個球體。粉色的部分是陰影體。