體繪制(Volume Rendering)概述之3:光線投射算法(Ray Casting)原理和注意要點(強烈推薦呀,講的很好)


轉自:http://blog.csdn.net/liu_lin_xm/article/details/4850609

摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文 名“GPU編程與CG語言之陽春白雪下里巴人”   

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 】空間,這個過程有可能導致數據精度的損失。基於如上的考慮,我將方向向量的計算放到片段着色程序中,通過視點和頂點位置進行計算。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM