介紹
這篇文章將介紹光線追蹤技術(shù)。在計算機圖形領(lǐng)域中,這種技術(shù)被普遍應(yīng)用于生成高質(zhì)量的照片級圖像。在為一個場景計算光照的時候,通過固定圖形渲染管線可以計算phong光照模型,由于該模型的特征,使得渲染的物體看起來有塑料的質(zhì)感。如果要渲染一個有金屬質(zhì)感且能反射周圍環(huán)境的物體,phong模型就無能為力了。和固定渲染管線相比,可編程圖形渲染管線的力能要強的多,雖然可以實現(xiàn)很多逼真的光照效果,比如利用環(huán)境貼圖來現(xiàn)實物體對環(huán)境的反射效果。但是這種環(huán)境反射只能反射出已經(jīng)保存在Cube Map中的圖像。在真實世界中,如果一個能反射周圍環(huán)境的物體周圍還有很多其他物體,它們就會相互反射。一般的環(huán)境貼圖技術(shù)達不到這樣的效果,于是在渲染照片級畫面的時候,就要用到光線追蹤的技術(shù)。文本還將利用c++面向?qū)ο蟮姆椒▉韺崿F(xiàn)光線追蹤。
原理
在介紹原理之前,先考慮一個問題:我們是怎樣看到真實世界中的物體的?我們能看到物體,是因為該物體上有反射光線到達我們的眼睛。沒有任何光線傳入眼睛,我們就看不到任何東西。我們還經(jīng)??吹揭粋€物體表面能反射另一個物體。這也是因為被反射物體表面的反射光線到達該物體表面后,該物體繼續(xù)將光線反射到我們的眼睛里,于是我們看到了該物體表面反射其他物體的效果。現(xiàn)在,我們將從物體表面出發(fā)最后到達眼睛的光線的方向反向。先來看看下面的Fig1,在 Fig1中是一個虛擬的場景,場景中有2個球和1個圓錐,白色的點代表光源,中間四邊形就是虛擬屏幕,屏幕上一個一個的小方格就代表像素,相機的位置代表觀察者眼睛的位置。
(a)
(b)
Fig1 光線追蹤場景
光線追蹤的原理就是從相機的位置發(fā)出一條條通過每一個像素的射線,如果該射線和場景中的物體相交,那么就可以計算出該交點的顏色,這個顏色就是對應(yīng)的像素的顏色。當(dāng)然,計算像素顏色的時候首先要計算出交點處所有與光照計算相關(guān)量,比如法線,入射光線和反射光線等等。
(b)
Fig2 光線和空間物體相交
在Fig2中可以看到,從相機出發(fā)的射線依次穿過每一個像素,圖中顯示出其中的三條。這些射線都與物體有交點,不同物體的交點計算方法也不一樣。射線與平面的交點計算方法和射線與球的交點計算方法是截然不同的。為了計算方便,這里就只以球為例。如果一個物體可以反射周圍的環(huán)境,那么當(dāng)一條射線與該物體相交后,射線還會在該點產(chǎn)生反射和折射等。例如在Fig2中,當(dāng)射線和藍色球相交后,光線會反射,反射的光線又可能和橙色圓錐和綠色球相交,所以我們能在藍色球的表面看到橙色的圓錐和綠色球。整個光線追蹤的原理就是這么簡單,但是實際操作起來又有很多要注意的地方。
實踐
用面向?qū)ο蟮姆椒▉韺崿F(xiàn)光線追蹤比使用面向結(jié)構(gòu)要來的容易一些。因為在光線追蹤的整個過程中,比較容易抽象出對象的共同特征,比如我們可以抽象出射線,物體,光源,材質(zhì)等等。當(dāng)然,最最基本的一個類就是向量類,在計算光照的時候向量很重要。在這里我們假設(shè)已經(jīng)實現(xiàn)了一個三維向量類GVector3,該類提供所有有關(guān)向量的操作。
除了向量,我們最先能想到一個關(guān)于射線的類,叫CRay。
對于一條射線最基本的就是它的出發(fā)點和方向,所以在CRay的類圖中,能看到兩個私有成員變量m_Origin和m_Direction,它們都是 GVector3類型。由于類的設(shè)計原則要滿足數(shù)據(jù)的封裝性,既然射線的出發(fā)點和方向都是私有的,那么就要提供公共的成員方法來訪問它們,于是我們還需要 set和get方法。最后,getPoint(double)方法是通過向射線的參數(shù)方程傳入?yún)?shù)t而獲得在射線上的點。實現(xiàn)了射線CRay類后,那么在使用光線追蹤計算每個像素顏色的時候,對于每一個像素都要創(chuàng)建一個CRay的實例。
for(int y=0; y<=ImageHeight; y++)
{
for(int x=0; x<=ImageWidth; x++)
{
double pixel_x = -20.0 +40.0/ImageWidth*x;
double pixel_y = -15.0 +30.0/ImageHeight*y;
GVector3 direction = GVector3(pixel_x, pixel_y,0)-CameraPosition;
CRay ray(CameraPosition, direction);
// call RayTracer function
}
}
從上面的代碼可以看到,兩個for循環(huán)用于掃描每一個像素,然后在循環(huán)里計算出每個像素的位置。如果我們假設(shè)Fig1中,四邊形屏幕處于xy平面,長和寬分別是40和30,且左上頂點坐標和右下頂點坐標分別為(-20,15,0)和(20,-15,0)。為了將該屏幕映射到實際分辨率為 800*600的窗口上,就要求出虛擬屏幕上每個像素的坐標pixel_x和pixel_y。然后對每一個像素都用一條射線穿過它,射線的方向自然就是像素的位置和相機位置的差向量的方向。要注意一點,實際窗口的分辨率比例要和虛擬屏幕長寬比例保持一致,這樣渲染出來的畫面看起來長寬比例才正確。
現(xiàn)在我們來考慮在場景中的物體。一個物體可能有很多可以描述它的特征,比如形狀,大小,顏色,材質(zhì)等等。使用面向?qū)ο蟮姆椒?,就需要將這些物體的共同特征抽象出來。下面是一個抽象出來的物體類GCObject。