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