摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU編程與CG語言之陽春白雪下里巴人”
1982 年2 月,美國國家科學基金會在華盛頓召開了科學可視化技術的首次會議,會議認為“科學家不僅需要分析由計算機得出的計算數據,而且需要了解在計算過程中的數據變換,而這些都需要借助於計算機圖形學以及圖像處理技術”。
---- 《三維數據場可視化》1.1 節科學計算可視化概述
自 20 世紀 80 年代科學計算可視化( Visualization in Scientific Computing )被提出后,三維體數據的可視化逐漸稱為發展的重點,並最終形成了體繪制技術領域。
一些文章,甚至是優秀碩博士論文庫上的文章,解釋體繪制概念時,通常都說 “ 體繪制技術是直接根據三維體數據場信息產生屏幕上的二維圖像 ” ,這種說法太過含糊,如果根據三維體數據場信息隨便產生一張圖像,難道也是體繪制嗎?我查找相關文獻后,發現這種說法是國外一些文獻的誤譯,例如, M.Levoy 在文章 “Display of surfaces from volume data”( 文獻【 14 】 ) 中提到 “volume rendering describes a wide range of techniques for generating images from three-dimensional scalar data” ,翻譯過來就是 “ 體繪制描述了一系列的 “ 根據三維標量數據產生二維圖片 ” 的技術 ” 。注意,人家文章中用的是 “ 描述 ” ,而不是對體繪制下定義。老實說,老外的這種說法雖然挑不出毛病,但是我依然感覺沒有落實到重點。
體繪制的核心在於 “ 展示體細節!而不是表面細節 ” 。我給出的定義是:依據三維體數據,將所有體細節同時展現在二維圖片上的技術,稱之為體繪制技術。利用體繪制技術,可以在一幅圖像中顯示多種物質的綜合分布情況,並且可以通過不透明度的控制,反應等值面的情況。
例如, CT 圖片中展示的是人體的肌肉和骨骼信息,而不是表面信息(那是照片)。所以理解體繪制和面繪制技術的區別的 , 一個很直觀的比喻是:普通照相機照出的相片和 CT 儀器拍出的 CT 照片,雖然都是二維圖片,但是展現的對象是不同的!
國外自上世紀 80 年代末以來,在體繪制技術方面已經取得了長足的進步,西門子、東芝、通用電器,都有對 GPU 編程領域以及體繪制技術進行研究,並將體繪制技術運用到醫療器材中。然而,體繪制技術在中國的發展,如果說還處於萌芽階段,實不為過!我在學習和研究過程中,在國內網站上甚至只找到了一個可用的體數據,還是國外代碼中附帶的演示數據,而國內的 openGPU 網站上關於體繪制的論壇板塊則是根本是空白。國外已經常用的醫療器材和算法,在中國還沒有成形,這實在是一種悲哀。一個諷刺的現象是,外國公司從事體繪制算法研究的卻不乏中國人,這更是一種悲哀。寫到這里,作為一名以 server the people 為畢生理想的有志青年,我有點傷感,有些沮喪,所以,還是先洗洗睡了,明天再來寫下面的章節。
14.1 體繪制與科學可視化
科學可視化技術是運用計算機圖形學、圖像處理、計算機視覺等方法,將科學、工程學、醫學等計算、測量過程中的符號、數字信息轉換為直觀的圖形圖像,並在屏幕上顯示的理論、技術和方法。
體繪制是科學可視化領域中的一個技術方向。如前所述,體繪制的目標是在一副圖片上展示空間體細節。舉例而言,你面前有一間房子,房子中有家具、家電,站在房子外面只能看到外部形狀,無法觀察到房子的布局或者房子中的物體;假設房子和房子中的物體都是半透明的,這樣你就可以同時查看到所有的細節。這就是體繪制所要達到的效果。
14.2 體繪制應用領域
人類發展史上的重大技術帶來的影響大致分為兩種:其一,技術首先改變生活本身,然后改變人類對世界的看法,例如電視、電話等;還有一種技術,是首先改變人類對世界的看法,然后改變生活本身,例如倫琴射線、望遠鏡。
體繪制技術應該屬於后者,通過改變所見,而改變生活。體繪制計算的重要意義,首先在於可以在醫療領域 server the people, 有助於疾病的診斷,這一點應該不用多說,計算機斷層掃描( CT )已經廣泛應用於疾病的診斷。醫療領域的巨大需求推動了體繪制技術的告訴發展,如果了解 CT 的工作原理,也就大致了解了體繪制技術原理和流程,所以本書在附錄 B 給出了醫學體繪制的有關文獻,作為補充閱讀資料,當您對體素、光線投射等術語缺乏感性認識時,可以參閱理解;其二,體繪制計算可以用於地質勘探、氣象分析、分子模型構造等科學領域。我在工作期間承擔的一個較大的項目便是有關 “ 三維氣象可視化 ” ,氣象數據通常非常龐大,完全可以號稱海量數據,每一個氣壓面上都有溫度、濕度、風力風向等格點數據,氣象研究人員希望可以同時觀察到很多氣壓面的情況,這時就可以采用體繪制技術,對每個切面(氣壓面)進行同時顯示。
體繪制技術也能用於強化視覺效果,自然界中很多視覺效果是不規則的體,如流體、雲、煙等,它們很難用常規的幾何元素進行建模,使用粒子系統的模擬方法也不能盡善盡美,而使用體繪制可以達到較好的模擬效果。如 圖 41 所示,這是使用體繪制技術進行煙的模擬效果。
14.3 體繪制與光照模型
盡管光照模型通常用於面繪制,但是並不意味着體繪制技術中不能使用光照模型。實際上 , 體繪制技術以物體對光的吸收原理為理論基礎,在實現方式上,最終要基於透明度合成計算模型。此外,經典的光照模型,例如 phong 模型, cook-torrance 模型都可以做為體繪制技術的補充,完善體繪制效果,增強真實感。
往往有初學者會分不清 “ 體繪制技術 ” 以及 “ 透明光照模型 ” 之間的區別。這個問題很有意思。實際上,體繪制技術與透明光照模型在感性認識上十分類似,在很多教程中對體繪制技術的闡述也涉及到透明物體。但是,透明光照模型,一般側重於分析光在透明介質中的傳播方式(折射,發散,散射,衰減等),並對這種傳播方式所帶來的效果進行模擬;而體繪制技術偏重於物體內部層次細節的真實展現。舉例而言,對於一個透明的三棱鏡,使用透明光照模型的目的在於 “ 模擬光的散射,折射現象(彩虹) ” ;而對於地形切片數據或者人體數據,則需要使用體繪制技術觀察到其中的組織結構。此外,在實現方式上,透明光照模型一般是跟蹤光線的交互過程,並在一系列的交互過程中計算顏色值;而體繪制技術是在同一射線方向上對體數據進行采樣,獲取多個體素的顏色值,然后根據其透明度進行顏色的合成。
總的來說,透明光照模型側重於光照效果展現,並偏向藝術化;而體繪制技術側重展現物質內部細節,要求真實!
不過,現在體繪制技術實際上也可以用於藝術領域,因為體繪制技術所使用的方法,實際上具有很強的通用性,尤其是傳統的 ray-cast 方法,完全可以應用到透明光照模型中(繪制煙霧等)。不同的技術之間會存在共融性,將技術和領域的關系近固化,是研究人員的大忌。科學史上很多前例都說明了一個事實:不同領域的交合點,往往會出現重大發現或發明。在愛因斯坦之前,又有誰知道時間、空間和質量之間的關系呢?
體數據( Volume Data )
學習任何一門技術,首先要弄清楚這項技術的起源以及數據來源。技術的起源也就是技術最原始的需求,最原始的發展動力,了解了這一點就了解了這項技術的價值。而了解一門技術的數據來源,就把握了技術的最初脈絡,是 “ 持其牛耳 ” 的一種方法,正如軟件工程中的數據流分析方法一般。
我很想說,體數據與面數據的區別,就好像一個實心的鐵球和一個空心的兵乓球的區別。不過這個比喻很顯然有點俗,很難讓人相信作者(我)是一個專業人士。於是我決定還是將與體數據相關的專業術語都闡述一遍。
不過,在此之前,我需要先消除大家的恐懼感,研究表明,動物對於未知事物總是存在恐懼感,這也是阻礙進一步學習的關鍵所在。體數據不是什么特別高深的火星符號,它是對一種數據類型的描述,只要是包含了體細節的數據,都可以稱之為體數據。舉個例子,有一堆混凝土,其中包含了碳物質( C )若干,水分子( H20 )若干,還有不明化學成分的膠狀物,你用這種混凝土建造了塊方磚,如果存在一個三維數組,將方磚 X 、 Y 、 Z 方向上的物質分布表示出來,則該數組可以被稱為體數據。不要小看上面這個比喻,體數據本質上就是按照這個原理進行組織的!
體數據一般有 2 種來源:
1. 科學計算的結果,如:有限元的計算和流體物理計算;
2. 儀器測量數據,如: CT 或 MRI 掃描數據、地震勘測數據、氣象檢測數據等。
與體數據相關的專業術語有:體素( Voxel )、體紋理( Volume Texture )。尤其要注意:所謂面數據,並不是說二維平面數據,而是說這個數據中只有表面細節,沒有包含體細節,實際上體數據和面數據的本質區別,在於是否包含了體細節,而不是在維度方面。
14.4.2 體素( Voxel )
Wikipedia 中對體屬 voxel 的介紹為:
A voxel (a portmanteau of the words volumetric and pixel) is a volume element, representing a value on a regular grid in three dimensional space. This is analogous to a pixel , which represents 2D image data in a bitmap 。
即 “ 體素,是組成體數據的最小單元,一個體素表示體數據中三維空間某部分的值。體素相當於二維空間中像素的概念 ” 。 圖 42 中每個小方塊代表一個體素。體素不存在絕對空間位置的概念,只有在體空間中的相對位置,這一點和像素是一樣的。
通常我們看到的體數據都會有一個體素分布的描述,即,該數據由 n*m*t 個體素組成,表示該體數據在 X 、 Y 、 Z 方向上分別有 n 、 m 、 t 個體素。在數據表達上,體素代表三維數組中的一個單元。假設一個體數據在三維空間上 256*256*256 個體素組成,則,如果用三維數組表示,就必須在每一維上分配 256 個空間。
在實際的儀器采樣中,會給出體素相鄰間隔的數據描述,單位是毫米( mm ),例如 0.412mm 表示該體數據中相鄰體素的間隔為 0.412 毫米 。
14.4.1 體紋理( Volume Texture )
體數據最主要的文件格式是 “ 體紋理( volume texture ) ” !故而,非常有必要對體紋理的概念進行詳細的闡述。
目前,學術性文章中關於體紋理的概念描述存在不小的混亂,很多書籍或者網頁資料沒有明確的區分 2d texture , 3d texture , volume texture 之間的區別。導致不少人認為 “ 只要是用於三維虛擬或仿真技術中的紋理都稱之為 3d texture” 。這是一個誤會。紋理上的 2 , 3 維之分本質上是根據其所描述的數據維數而定的,所謂 2d texture 指的是紋理只描述了空間的面數據,而 3d texture 則是描述了空間中的三維數據。 3d texture 另一個較為學術化的名稱是: volume texture 。文獻【 22 】上對體紋理的定義是:
3D texture (Three Dimensional Texture), also known as "volume texture," is a logical extension of the traditional (and better known) 2D texture. In this context, a texture is simply a bitmap image that is used to provide surface coloring for a 3D model. A 3D texture can be thought of as a number of thin pieces of texture used to generate a three dimensional image map. 3D textures are typically represented by 3 coordinates.
翻譯成中文就是 “ 三維紋理,即體紋理,是傳統 2D 紋理在邏輯上的擴展。二維紋理是一張簡單的位圖圖片,用於為三維模型提供表面點的顏色值;而一個三維紋理,可以被認為由很多張 2D 紋理組成的,用於描述三維空間數據的圖片。三維紋理通過三維紋理坐標進行訪問 ” 。
從上面這句話,可以得到兩點信息:
1. 三維紋理和體紋理是同一概念;三維紋理和二維紋理是不同的;
2. 三維紋理通過三維紋理坐標進行訪問。
這時可能會有人提出問題了,圖片都是平面的,怎么能表示三維數據?請注意,我們通常所看到的圖片確實都是平面的,但是並不意味着 x,y 平面上的像素點不能存放三維數據,舉一個例子:在高級語言編程中,我們完全可以用一維數組去存放三維數組中的數據,只要按照一定規則存放即可!
按照一定規則將三維數據存放在 XY 像素平面所得到的紋理,稱之為 volume texture 。
體數據通常是由 CT 儀器進行掃描得到的,然后保存在圖片的像素點上。目前國際上比較常用的體紋理格式有,基於 DirectX 的 .dds 格式和 .raw 格式。注意,很多人會將 .raw 格式當作攝像器材使用的那種格式,其實這兩個格式的后綴雖然都是 .raw ,但是其數據組織形式是不同的。用於體紋理的 .raw 格式,存放的是三維數據,用於攝像器材的 .raw 格式只是普通的二維圖片。 圖 43 從左到右分別是 University of Tübingen ( Germany )、 Viatronix Inc.(USA) 、 Walter Reed Army Medical Center (USA) 三家機構的通過儀器掃描得到的體紋理數據的體繪制圖片。
這三個體紋理數據的描述分別是: 256 x 320 x 128 /0.66, 0.66, 0.66 ; 512 x 512 x 174/0.8398, 0.8398, 3.2 ; 512 x 512 x 463/0.625, 0.625, 1.0 。
由於在國內的網站上很難找到體數據,所以下面我給出幾個國外的網址,這些網址提供用於教學和研究只用的體紋理數據(只能用於教學和研究)。
http://wwwvis.informatik.uni-stuttgart.de/~engel/pre-integrated/data.html
http://www9.informatik.uni-erlangen.de/External/vollib/
http://www.volren.org/
14.5 體繪制算法
國際上留下的體繪制算法主要有:光線投射算法( Ray-casting )、錯切 - 變形算法( Shear-warp )、頻域體繪制算法( Frequency Domain )和拋雪球算法( Splatting )。其中又以光線投射算法最為重要和通用。
究其原因,無外乎有三點:其一,該算法在解決方案上基於射線掃描過程,符合人類生活常識,容易理解;其二,該算法可以達到較好的繪制效果;其三,該算法可以較為輕松的移植到 GPU 上進行實現,可以達到實時繪制的要求。
本書的第 15 章將重點闡述光線投射算法。
15.1 光線投射算法原理
光線投射方法是基於圖像序列的直接體繪制算法。從圖像的每一個像素,沿固定方向(通常是視線方向)發射一條光線,光線穿越整個圖像序列,並在這個過程中,對圖像序列進行采樣獲取顏色信息,同時依據光線吸收模型將顏色值進行累加,直至光線穿越整個圖像序列,最后得到的顏色值就是渲染圖像的顏色。
為什么在上面的定義是穿越 “ 圖像序列 ” ,而不是直接使用 “ 體紋理 ” ?原因在於,體數據有多種組織形式,在基於 CPU 的高級語言編程中,有時並不使用體紋理,而是使用圖像序列。在基於 GPU 的着色程序中,則必須使用體紋理。這里所說的圖像序列,也可以理解為切片數據。
尤其要注意:光線投射算法是從視點到 “ 圖像序列最表面的外層像素 ” 引射線穿越體數據,而不少教程中都是糊里糊塗的寫到 “ 從屏幕像素出發 ” ,這種說法太過簡單,而且很容易讓人誤解技術的實現途徑,可以說這是一種以訛傳訛的說法!從屏幕像素出發引出射線,是光線跟蹤算法,不是光線投射算法。
體繪制中的光線投射方法與真實感渲染技術中的光線跟蹤算法有些類似,即沿着光線的路徑進行色彩的累計。但兩者的具體操作不同。首先,光線投射方法中的光線是直線穿越數據場,而光線跟蹤算法中需要計算光線的反射和折射現象。其次,光線投射算法是沿着光線路徑進行采樣,根據樣點的色彩和透明度,用體繪制的色彩合成算子進行色彩的累計,而光線跟蹤算法並不刻意進行色彩的累計,而只考慮光線和幾何體相交處的情況;最后,光線跟蹤算法中光線的方向是從視點到屏幕像素引射線,並要進行射線和場景實體的求交判斷和計算,而光線投射算法,是從視點到物體上一點引射線( 16.1.2 節會進行詳細闡述),不必進行射線和物體的求交判斷。
上述文字,對於光線投射算法的描述可能太過簡單,會引起一些疑惑,不過這是正常的,有了疑惑才會去思考解決之道,最怕看了以后沒有任何疑惑,那只是浮光掠影似的一知半解,而不是真正的了然於胸。
15.1.1 吸收模型
幾乎每一個直接體繪制算法都將體數據當作 “ 在某一密度條件下,光線穿越體時,每個體素對光線的吸收發射分布情況 ” 。這一思想來源於物理光學,並最終通過光學模型( Optical Models )進行分類描述。為了區別之前的光照渲染模型,下面統一將 Optical Model 翻譯為光學模型。
文獻【 15 】中對大多數在直接體繪制算法中使用的重要光學模型進行了描述,這里給出簡要概述。
1. 吸收模型( Absorption only ):將體數據當作由冷、黑的體素組成,這些體素對光線只是吸收,本身既不發射光線,也不反射、透射光線;
2. 發射模型( Emission only ):體數據中的體素只是發射光線,不吸收光線;
3. 吸收和發射模型( Absorption plus emission ):這種光學模型使用最為廣泛,體數據中的體素本身發射光線,並且可以吸收光線,但不對光線進行反射和透射。
4. 散射和陰影模型( Scattering and Shading/shadowing ):體素可以散射(反射和折射)外部光源的光線,並且由於體素之間的遮擋關系,可以產生陰影;
5. 多散射模型( Multiple Scattering ):光線在被眼睛觀察之前,可以被多個體素散射。
通常我們使用 吸收和發射模型( Absorption plus emission )。為了增強真實感,也可以加上陰影(包括自陰影)計算。
15.2 光線投射算法若干細節之處
15.2.1 光線如何穿越體紋理
這一節中將闡述光線如何穿越體紋理。這是一個非常重要的細節知識點,很多人就是因為無法理解 “ 體紋理和光線投射的交互方式 ” 而放棄學習體繪制技術。
前面的章節似乎一直在暗示這一點:通過一個體紋理,就可以進行體渲染。我最初學習體繪制時,也被這種暗示迷惑了很久,后來查找到一個國外的軟件,可以將體紋理渲染到立方體或者圓柱體中,這時我才恍然大悟:體紋理並不是空間的模型數據,空間體模型(通常是規則的立方體或圓柱體)和體紋理相互結合才能進行體渲染。
舉例而言,我們要在電腦中看到一個紋理貼圖效果,那么至少需要一張二維的紋理和一個面片,才能進行紋理貼圖操作。這個面片實際上就是紋理的載體。
同理,在體繪制中同樣需要一個三維模型作為體紋理的載體,體紋理通過紋理坐標(三維)和模型進行對應,然后由視點向模型上的點引射線,該射線穿越模型空間等價於射線穿越了體紋理。
通常使用普通的立方體或者圓柱體作為體繪制的空間模型。本章使用立方體作為體紋理的載體。
注意:體紋理通過紋理坐標和三維模型進行對應,考慮到 OpenGL 和 Direct3D 使用的體紋理坐標並不相同,所以寫程序時請注意到這一點。
圖 44 展示了體紋理坐標在立方體上的分布,經過測試,這種分布關系是基於 OpenGL 的。在宿主程序中確定立方體 8 個頂點的體紋理坐標,注意是三元向量,然后傳入 GPU ,立方體 6 個面內部點的體紋理坐標會在 GPU 上自動插值得到。
根據視點和立方體表面點可以唯一確定一條射線,射線穿越整個立方體等價於穿越體數據,並在穿越過程中對體數據等距采樣,對每次得到的采樣數據按照光透公式進行反復累加。這個累加過程基於 11 章講過的透明合成公式,不過之前只是進行了簡單的講解,在本章中將針對透明度,透明合成,以及排序關系做全面闡述。
15.2.2 透明度、合成
透明度本質上代表着光穿透物體的能力,光穿透一個物體會導致波長比例的變化,如果穿越多個物體,則這種變化是累加的。所以,透明物體的渲染,本質上是將透明物體的顏色和其后物體的顏色進行混合,這被稱為 alpha 混合技術。圖形硬件實現 alpha 混合技術,使用 over 操作符。 Alpha 混合技術的公式如下所示:
其中,as 表示透明物體的透明度, cs表示透明物體的原本顏色, cd表示目標物體的原本顏色, co 則是通過透明物體觀察目標物體所得到的顏色值。
如果有多個透明物體,通常需要對物體進行排序,除非所有物體的透明度都是一樣的。在圖形硬件中實現多個透明物體的繪制是依賴於 Z 緩沖區。在光線投射算法中,射線穿越體紋理的同時也就是透明度的排序過程。所以這里存在一個合成的順序問題。可以將射線穿越紋理的過程作為采樣合成過程,這是從前面到背面進行排序,也可以反過來從背面到前面排序,毫無疑問這兩種方式得到的效果是不太一樣的。
如果從前面到背面進行采樣合成,則合成公式為:
其中, Ci 和 Ai分別是在體紋理上采樣所得到的顏色值和不透明度,其實也就是體素中蘊含的數據; deta Ci和 deta Ai表示累加的顏色值和不透明度。
注意,很多體紋理其實並沒有包含透明度,所以有時是自己定義一個初始的透明度,然后進行累加。
如果從背面到前面進行采樣合成,則公式為:
15.2.3 沿射線進行采樣
如 圖 45 所示,假定光線從 F 點投射到立方體中,並從 L 點投出,在立方體中穿越的距離為 m 。當光線從 F 點投射到立方體中,穿越距離為 n ( n<m )時進行采樣,則存在公式:
其中 Tstart表示立方體表面被投射點的體紋理坐標; d表示投射方向;detal 表示采樣間隔,隨着 n 的增加而遞增;t 為求得的采樣紋理坐標。通過求得的采樣紋理坐標就可以在體紋理上查詢體素數據。直到 n>m ,或者透明度累加超過 1 ,一條射線的采樣過程才結束。
下面總結一下:首先需要一個確定了頂點紋理坐標的三維立方體,光線穿越立方體的過程,就是穿越體紋理的過程,在整個穿越過程中,計算采樣體紋理坐標,並進行體紋理采樣,這個采樣過程直到光線投出立方體或者累加的透明度為 1 時結束。
我想這個過程應該不復雜,大家一定要記住:紋理坐標是聯系三維模型和體紋理數據之間的橋梁,通過計算光線穿越三維模型,可以計算體紋理在光線穿越方向上的變化,這就是計算采樣紋理坐標的方法。
高中時學習物理的力學部分,最初一直處於渾渾噩噩的狀態,遇到應用題不知道從何處入手,后來看一本參考資料講到 “ 加速度是聯系力和運動狀態的橋梁,遇到題目首先分析加速度的求法 ” ,由此舉一反三,不再感覺物理難學。所以在此我也借用那句話,總結紋理坐標的作用。
現在還存在一個問題:如何知道光線投射出了立方體?這個問題等價於計算光線在立方體中穿越的距離 m 。在下一節中將進行闡述。
附:在 OpenGL 和 DirectX 中,體紋理坐標的分布規則是不一樣的,所以要針對自己當前使用的 profile 來確定頂點體紋理坐標的設置。這也從側面說明了, Cg 語言是基於 OpenGL 和 DirectX 的。
15.2.2 如何判斷光線投射出體紋理
上一節闡述過:光線投射出體紋理,等價於光線投射出立方體。所以如何判斷光線投射出體紋理,可以轉換為判斷光線投射出立方體。
首先計算光線在立方體中入射到出射的行進距離 m ,然后當每次采樣體紋理時同時計算光線在立方體中的穿越距離 n ,如果 n>=m ,則說明光線射出立方體。給定光線方向,以及采樣的距離間隔,就可以求出光線在立方體中的穿越距離 n 。
如果是在 CPU 上,距離 m 很容易通過解析幾何的知識求得,直接求出光線和幾何體的兩個交點坐標,然后計算歐幾里德距離即可。但是在 GPU 上計算光線和幾何體的交點是一個老大難的問題,尤其在幾何體不規則的情況下;此外,就算是規則的幾何體,光線與其求交的過程也是非常消耗時間,所以通過求取交點然后計算距離的方法不予采用。
請思考一下,在 GPU 中確定點和點之間順序關系的還有哪個量?深度值(我自問自答)。
在 GPU 中可以間接反應點和點之間關系的有兩個量,一個是紋理坐標,另一個就是深度值。通常在渲染中會進行深度剔除,也就是只顯示深度值小的片段。不過也存在另外一個深度剔除,將深度值小的片段剔除,而留下深度值最大的片段(深度值的剔除方法設置,在 OpenGL 和 Direct 中都有現成函數調用)。如果使用后者,則場景中渲染顯示的是離視點最遠的面片集合。
所以,計算距離 m 的方法如下:
1. 剔除深度值較大的片段(正常的渲染狀態),渲染場景深度圖 frontDepth (參閱第 14 章),此時 frontDepth 上的每個像素的顏色值都代表“某個方向上離視點最近的點的距離”;
2. 剔除深度值較小的片段,渲染場景深度圖 backDepth , backDepth 上的每個像素的顏色值都代表“某個方向上離視點最遠的點的距離”;
3. 將兩張深度圖上的數據進行相減,得到的值就是光線投射距離 m 。
如果認真實現過第 14 章講的 shadow Map 算法,對這個過程應該不會感到太復雜。可能存在的問題是:背面渲染很多人沒有接觸過。這里對背面渲染的一些細微之處進行闡述,以免大家走彎路。
通常,背面的面片(不朝向視點的面片)是不會被渲染出來的,圖形學基礎比較好的同學應該知道,三個頂點通常按逆時針順序組成一個三角面,這樣做的好處是,背面面片的法向量與視線法向量的點積為負數,可以據此做面片剔除算法(光照模型實現中也常用到),所以只是改變深度值的比較方法還不夠,還必須關閉按照逆 / 順時針進行面片剔除功能,這樣才能渲染出背面深度圖。 圖 46 是立方體的正面和背面深度圖。
附:在很多教程上,都是將 frontDepth 和 backDepth 相減后的值,保存為另外一個紋理,稱之為方向紋理,每個像素由 r 、 g 、 b 、 a 組成,前三個通道存儲顏色值,最后的 a 通道存放距離值,我覺得這個過程稍微繁瑣了些,此外由於方向向量可能存在負值,而顏色通道中只能保存正值,所以必須將方向向量歸一化到【 0 , 1 】空間,這個過程有可能導致數據精度的損失。基於如上的考慮,我將方向向量的計算放到片段着色程序中,通過視點和頂點位置進行計算。
算法流程
圖 47 展示了使用光線投射算法進行體繪制的實現流程。
首先要渲染出正向面深度圖和背向面深度圖,這是為了計算射線穿越的最大距離,做為循環采樣控制的結束依據;然后在頂點着色程序中計算頂點位置和射線方向,射線方向由視線方向和點的世界坐標決定,其實射線方向也可以放在片段着色程序中進行計算。然后到了最關鍵的地方,就是循環紋理采樣、合成。
每一次循環都要計算新的采樣紋理坐標和采樣距離,然后進行顏色合成和透明度累加,如果采樣距離超過了最大穿越距離,或者透明度累加到 1 ,則循環結束。將合成得到的顏色值輸出即可。
圖 48 給出了使用光線投射算法進行體繪制的效果圖:
15.4 光線投射算法實現
本節給出光線投射算法的着色程序實現代碼。依然是分為三個部分:結構體、頂點着色程序和片段着色程序。
代碼 22 光線投射算法結構體
struct VertexIn
{
float4 position : POSITION;
float4 texCoord: TEXCOORD;
};
struct VertexScreen
{
float4 position : POSITION;
float4 worldPos : TEXCOORD0;
float4 projPos : TEXCOORD1;
float4 texCoord : TEXCOORD2;
};
代碼 23 光線投射算法頂點着色程序
VertexScreen main_v(VertexIn posIn,
uniform float4x4 world,
uniform float4x4 worldViewProj,
uniform float4x4 texViewProj)
{
VertexScreen posOut;
posOut.position = mul(worldViewProj, posIn.position);
posOut.worldPos = mul(world,posIn.position);
posOut.projPos = mul(texViewProj, posOut.worldPos);
posOut.texCoord = posIn.texCoord;
return posOut;
}
代碼 24 光線投射算法片段着色程序
void main_f(VertexScreen posIn,
uniform float3 eyePosition,
uniform sampler3D volumeTex: register(s0),
uniform sampler2D frontDepthTex: register(s1) ,
uniform sampler2D backDepthTex: register(s2) ,
out float4 result : COLOR)
{
// 根據視點和當前頂點世界坐標計算方向
float3 dir = posIn.worldPos.xyz-eyePosition;
dir = normalize(dir);
float3 deltaDir = float3(0.0, 0.0, 0.0);
// 獲取當前頂點的三維紋理坐標
float3 tex = posIn.texCoord.xyz;
float2 uvDelta;
uvDelta.x = 0.0;//ddx( tex ).x;
uvDelta.y = 0.0;//ddy( tex ).y;
// 取出深度間隔值 , 並設置采樣間隔
float2 uv= posIn.projPos.xy/posIn.projPos.w;
float frontDis = tex2D(frontDepthTex,uv).x;
float backDis = tex2D(backDepthTex,uv).x;
float len = backDis-frontDis;
// 初始化顏色值、采樣值、透明度
float3 norm_dir = normalize(dir);
float stepsize = 0.01;
float delta = stepsize;
float3 delta_dir = norm_dir * delta;
float delta_dir_len = length(delta_dir);
float3 vec = posIn.texCoord.xyz;
float4 col_acc = float4(0,0,0,0);
float alpha_acc = 0;
float length_acc = 0;
float4 color_sample;
float alpha_sample;
for(int i = 0; i < 800; i++){
color_sample = tex3D(volumeTex,vec);
alpha_sample = color_sample.a * stepsize;
col_acc += (1.0 - alpha_acc) * color_sample * alpha_sample * 3;
alpha_acc += alpha_sample;
vec += delta_dir;
length_acc += delta_dir_len;
if(length_acc >= len || alpha_acc > 1.0) break; // 采樣循環控制條件
}
result.xyz = col_acc.xyz*2.0+float3(0.2,0.2,0.2);
result.w = col_acc.w;
}
15.5 本章小結
本書的第14 、15 章闡述了體繪制中光線投射算法的基本原理和實現流程。實際上,在此基礎上可以對光線投射算法加以擴展,例如將光線投射算法和陰影繪制算法相結合,可以渲染出真實感更強的圖像。
此外,有些體數據是中間是空的,在射線方向上進行采樣時需要跳過空區域,這其中也需要額外的算法處理,在英文中稱為“Object-Order Empty Space Skipping ”。
目前我所發現關於體繪制以及光線投射算法最好的教材是Markus Hadwiger 等人所寫的“Advanced Illumination Techniques for GPU-Based Volume Raycasting ”。此書發表在SIGGRAPH ASIA2008 上,是目前所能找到最新也是非常權威的教材,共166 頁。英文閱讀能力比較好的同學可以嘗試着看一下。
本章已經是此書的最后一章,最后希望中國的計算機科學可以真正上升到科學研究的層次,而不是一直在混沌中熱衷做泥瓦匠的工作。
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://www.cnblogs.com/captainbed