Ray-tracing 中的相機(眼睛)是所有光束的起點,從相機成像平面出發的光束如果能夠經由折射、反射等等最終到達光源的那些「存活」的光束,才對最終的影像有影響的光束。這種與現實物理相反的設計(從光源發出光並追蹤那些存活到相機成像平面的光束)是為了減少計算量。
實作真實相機光學系統
相機的光學系統決定了最終成像的樣貌,比如廣角、魚眼、正交投影等等樣貌,pbrt-v2 中實做了最常見的幾個,包含了 perspective camera, orthographic camera,原始程式碼在 src/camera
之下。
實際上的相機的光學系統通常都包含了多個透鏡,以此來達成比較複雜的成像(或是減少透鏡的像差)。
上圖是描述光學系統各個透鏡面的相關參數,從成像平面出發的 eye-ray 會在這個光學系統折射多次之後才會進入場景中,而這些光束又會跟場景的物件相交、反射等等,就如同我之前寫的這篇 改善 pbrt 中的 heightfield shape,在做一樣的事。
所以,要模擬真實相機的光學系統,其實就是幾個步驟:
- 在成像平面上與第一個透鏡面上各取一點,相連產生 eye-ray
- 找出 eye-ray 與第一個透鏡面的交點,沒交點就結束
- 找出折射後的新 eye-ray
- 重覆 2, 3 直到 eye-ray 離開光學系統進入場景為止
以 pseudo-code 表示,大概可以寫成這樣:
1 | filmP = random point on film |
其實也可以借助一些除錯工具來視覺化光束的折射行為,這邊我使用 vdb 來畫出整個光學系統並且追蹤光束的折射行為。
(關於如何利用 vdb 除錯,可以參考 vdb - Debugging visual programs)
除此之外,也需要計算每條 ray 的權重,根據論文[2]所說是如下公式:
結果
我嘗試渲染大張一點的圖並且讓每個像素的採樣夠多次,希望能夠讓結果圖漂亮一點。
代價就是一張圖要跑好幾個小時……
加速
每個透鏡面基本上是以部份的球面來模擬,要求交點很容易,因為 src/shape/shpere.cpp
已經實做求交點了,可以直接創一個 Sphere object ,然後再 s.Intersect(r, &thit, &rayEpsilon, &dg)
來取得交點。
這樣寫簡單易懂,三兩下解決求交點這件事,但其實效能並不好,因為只為了求一個交點卻建構了一個完整的 shpere shape ,稍微有點浪費……
改善方式可以藉由複製貼上 Shpere::Intersect()
並加以改寫,就可以省去這樣的 overhead
實際上效能大約差 2 倍。
各種坑
- RasterToCamera
在GenerateRay()
中,sample.imageX
sample.imageY
是在 Raster space 上,需轉至 Camera space,Raster/Camera space 之間關係如圖(省略 z 軸),可以看出來轉換方式大概就是:上下翻轉、平移、縮小。 - Ray minT, maxT
在GenerateRay()
中產生的 ray 記得參數要給好給滿,之前因為沒給值到 min/maxT 導致結果有坑坑巴巴的破圖… - Total reflection
我使用 Snell’s law 計算折射後的新方向,在公式中,若發生全反射時會讓其中一項產生虛數,程式就整個爆炸了。
所以記得要注意別把負數開根號了。
References
- 圖片取自維基百科 Ray-tracing
- A Realistic Camera Model for Computer Graphics, SIGGRAPH 1995