身為一個計算機圖形學領域的小白,最近由于課程的緣故學習了一些光線追蹤方面的知識,并實現了基本的代碼,現在分享給大家。希望讀者能從一無所知到稍稍理解光線追蹤的原理和意義,我的目的就達到了。
光線追蹤的效果是生成一幅與真實世界高度相似的圖片,先上結果圖:
圖中包含4個球體、1個正方體和地面。其中,綠球具有光滑表面,可以看到上面反射的其它物體的圖案;紅球具有光滑表面而且透明,可以透過它看到后面的藍球;青球、黃色正方體和地面的表面是粗糙的,既沒有反射也沒有透射。另外,根據陰影的方向可以判斷出光源位于屏幕的左上方。
以上就是圖中包含的所有信息。接下來,我們將一步步揭開它的神秘面紗。
渲染
我們常常聽到“渲染”這個詞,卻很少人清楚它的真正含義。當我們想要看到一個三維場景時,第一步要建立它的三維模型。這個模型存儲在計算機中,它是真正的三維場景,但是我們看不到效果。要想從各個視角來觀察這個三維模型,就要把它渲染成二維圖片。就好像我們所處的世界是一個巨大的三維場景,無論你看或不看,它就在那里。當我們用眼睛來觀察這個世界時,三維世界在視網膜上形成二維的像,這個像就是渲染的結果。
全局光照
對人來說,射入眼睛的光自動拼合成一幅完整的圖像,這是自然而然是事情。但是計算機只有三維場景沒有光,怎么辦?
我們自己創造光。
我們完全仿照真實世界的物理規律,讓三維場景中的光源向四面八方發出無數條光線,根據反射、折射、散射等等光學定律追蹤每一條光線的軌跡。這樣得到的效果稱為全局光照,相對于局部光照,全局光照中任何一個物體都受到直接的光源照射和間接的來自其它物體反射光的照射。可以想象的是,這樣追蹤的結果是無窮無盡的,最初發出的每一條光線都會進一步衍生出無數條光線。在真實世界中,它們可以按照物理規律安靜的發生,可是在計算機中我們實在無法處理如此大的計算量。
幸運的是,對于旨在渲染一幅圖片的我們來說,大部分光線都是毫不相干的,我們只要追蹤那些匯聚入攝像機鏡頭的光線就可以了。這種方法就是本文所講的光線追蹤。
光線追蹤
與上一段提到方法恰好相反,我們讓光線從攝像機鏡頭出發,而且只發出特定角度的光線。這些光線恰好每一條對應最后生成的二維圖片中的一個像素,這樣的話,生成一幅640x480的位圖就只需要307200條光線。
下圖展示了生成一個9x8的位圖的光線追蹤原理。在一個三維笛卡爾坐標系中(圖中沒有畫出坐標軸的位置,可以任意選定),相機(Camera)和圖像(Image)的位置一旦確定,圖像中所能呈現的三維場景的范圍也就隨之確定。從相機發出72條光線,穿過圖像中每個小方格的中心。對每一條光線,找到場景中與之最近的交點。在本例中,一些光線會與球面相交,如果該球體既能反射又能透射(這些性質作為物體的固有屬性事先已在場景中定義),光線在交點處將會生成兩條新的反射光線和折射光線。于是,這些新的光線將按照與最初的光線相同的處理辦法,一直遞歸處理下去。但是,遞歸總要有結束條件的,當光線不與任何物體相交,即射向了無窮遠處,遞歸就可以結束了。然而總有可能出現無論如何總有物體與之相交的情況,那么我們就設置一個最大遞歸深度,一旦超過了這個深度就強制停止遞歸。
遞歸了半天不要忘記我們的初衷,我們是為了在圖像上成像而做的光線追蹤。圖像上每個點代表一個像素,它的RGB分量決定了該點的顏色。而我們做光線追蹤的目的就是得到最初穿過這個像素的光線的真實RGB分量。在整個遞歸過程中,考慮真實世界的物理規律,入射光線的顏色與入射角、物體顏色、反射光線顏色和透射光線顏色有關,它們之間的關系可以用菲涅爾公式來描述,在此不做具體解釋。最終,我們遞歸的結果相當于把所有反射和折射的效果都融合到了最初穿過圖像的光線上,因此,把這些光線的顏色存入位圖的相應位置就得到了一幅圖像。
這就是光線追蹤的基本原理。
然而,懂得了這些原理并不代表你就能寫出一套光線追蹤的程序。因為實際實現過程中存在的問題很多很多,絕不是百十行代碼就能搞定的。我也不是做計算機圖形學方面研究的,對光線追蹤的理解還很是膚淺。因此本文不再詳細講解具體的實現過程,想要更進一步了解的可以訪問參考資料中列出的前兩篇文章,這兩位作者是計算機圖形學領域的專家,我從他們那里受益頗多。
另外,需要指出的是,雖然光線追蹤已經盡可能地降低了計算量,但目前的計算機硬件并不支持對其加速,因此做不到實時渲染,只能用于離線渲染。
代碼實現
無論如何還是應該提一下實現代碼,不然對讀者來說本文就太過空洞了。
首先,我們用Solid
類來表示三維場景中的物體,這是一個物體的基類,特定的形狀如球體、正方體通過繼承該類并覆寫必要的方法來定義。在本例中,我們定義了球體Sphere
和立方體Cube
兩個派生類。它們需要重寫Solid
中的intersect
和nhit
方法。intersect
方法用于判斷給定光線是否與本物體相交,且給出兩個交點(射入和射出)的距離。nhit
方法用于計算給定交點處的法線方向。球體和立方體分別重新實現了這兩個方法。
有了三維場景之后,我們就可以開始光線追蹤。調用render
函數完成整個光線追蹤的過程,并生成一幅圖片。可以通過render
函數的參數調整相機和圖像所處位置,以達到變換視角的效果。在render
中,對每一條光線遞歸調用trace
函數,返回值為該光線的顏色。在trace
中遍歷場景中的所有物體,找到交點并計算反射和折射光線,進入下一次遞歸。
我把完整代碼上傳到了GitHub上,大家可以前往下載。渲染結果是一個ppm格式的文件,為了直接顯示該文件,我使用了opencv庫。因此,如果你的電腦中沒有安裝opencv,代碼可能會報錯。那么你只需要刪除showpicture這個文件,并在主函數中去掉showpicture(file)
這一行代碼即可。運行結束后在工程目錄中找到生成的1.ppm文件,使用Photoshop打開,或到https://www.coolutils.com/online/PPM-to-PNG 上面在線轉換成其它圖片格式就可以看到效果了。
其它效果展示
如果我們想讓畫面動起來,有兩種方式,讓場景中的物體移動或讓觀察視角移動。像連拍照片一樣在移動的同時不斷地執行光線跟蹤,生成一幀幀圖片,再將它們連接起來就形成了一段視頻。下面給出兩個使用該場景制作出的視頻片段。
視頻1:視角移動
視頻2:光源移動
需要改進的地方
相對于其它成功的光線跟蹤作品來說,本文展示的效果有些相形見絀,尚有許多需要改進的地方。
粗糙物體表面受到光照的部分亮度完全一致,區分不開。比如正方體的左表面和上表面顏色完全一樣,根本看不到棱的存在。這是因為我們對粗糙表面的處理太簡單,只考慮被遮擋和不被遮擋兩種情況。然而現實世界中,粗糙表面的顏色和亮度不光與光源有關,還與其他物體反射過來的光線顏色和亮度有關。改進方法是使用一種稱為Path Tracing的技術,這會產生比光線追蹤更好的效果。感興趣的讀者可以參考《Path Tracing介紹》這篇文章。
給物體表面添加紋理是個常見的話題。這需要做一些額外的工作,在參考資料的第二篇中講解了如何添加紋理。
最后,我想說計算機圖形學是一個非常大的學科,而且非常難。但它又極其重要,現在我們看到的屏幕上各種炫麗的畫面都要歸功于計算機圖形學的貢獻。因此感興趣的同學可以深入研究,這里面大有可以施展才華之處。
參考資料
光線追蹤算法綜述 Manster
用JavaScript玩轉計算機圖形學(一)光線追蹤入門 Milo Yip
Path Tracing介紹 Manster