光線追蹤中的透視畸變如何矯正?

更新=======================================================
改了下鏡頭的位置以及鏡頭遠近,調整了物體的大小以及相對位置,效果好了很多,如下圖,不過當兩個球離得稍遠時還是會有明顯的畸變。

原問題=======================================================
剛開始寫簡單的光線追蹤,如圖,相機是看向世界中心坐標的,藍色的球體因為位於中心坐標附近,所以變化不明顯,而右邊綠色的球體明顯有了透視畸變,請問這種問題該如何解決?是不是我的castray的代碼有問題?代碼如下

castRay相關代碼:

Vec3f Y(0, 1, 0);
Vec3f lookAt(0, 0, 0);
//camara model
Vec3f camOri(0, 4, 20);
Vec3f camDir((lookAt - camOri).normalize());
Vec3f camRight(camDir.cross(Y).normalize());
Vec3f camUp(camRight.cross(camDir).normalize());
float aspect = width * 1.0 / height;
for (unsigned int x = 0; x &< width; ++x) { fprintf(stderr, " Rendering %3.2f%%", (x + 1) * 1.0 / width * 100); for (unsigned int y = 0; y &< height; ++y) { unsigned int k = y * width + x; float xx = 0, yy = 0; //adjust if (aspect &> 1) {
xx = ((2 * (x + 0.5) / width) - 1) * aspect;
yy = 1 - (2 * (y + 0.5) / height);
}
if (aspect &< 1) { xx = ((2 * (x + 0.5) / width) - 1); yy = 1 - (2 * (y + 0.5) / height) / aspect; } if (aspect == 1) { xx = (2 * (x + 0.5) / width) - 1; yy = 1 - (2 * (y + 0.5) / height); } Vec3f raydir = (camDir + camRight * xx + camUp * yy).normalize(); pixel[k] = trace(camOri, raydir, objects, 0); } }


這跟光線跟蹤沒關係,跟攝像機設置有關係。不論你用光線跟蹤還是光柵化,都會這樣。調fov吧。


一般的攝像機模型使用透視投影,可以說是模仿理想的直線性鏡頭 (rectilinear lens)。這是指,場景中的直線經過投影后,在投影平面上仍然是直線。基於這個約束,必然會導致題主所說,畫面中心以外區域的透視變形 (perspective distortion) 現象。(題主用立方體代替球體就能明白。)

另一種投影是使用魚眼投影 (fisheye projection),例如 equidistant fisheye。但它們不再是直線性鏡頭,直線投影后會變成曲線。用光線追蹤是很容易實現的,光柵化就只能以後處理實現。

正如把地球畫成平面世界地圖,不同的平面投影只會有不同的變形,不可能有完美的選擇。只有放棄投影至平面才可解決這問題,例如用地球儀。而在圖形學上,可以用穹頂投影機,或更常見的 VR HMD,就可避免平面投影的變形。


畸變這麼明顯應該是你的視錐角度設置的不好導致的。不過畸變這種事情,如果你是用這種投影方法的話,是無法避免的。照相機也是這樣。

至於人眼的投影比較複雜。你經過觀察就會發現,如果你直視一條很長的橫樑的中間,兩邊其實是彎曲的。如果要近似這種效果,可以在人的面前放一個巨大的球體,然後光線首先平行於「前」方向射到球體,計算那一點的法線,然後改為按照法線的方向射出去。


這是一道幾何題:

無畸變=等距映射,等距(isometric)字面的意思就是等度量,即保持像的每一個局部等尺度並且保角(亦稱共形). 對於題主的問題而言,二維曲面局部的度量由一個張量(2	imes2數組)決定:g_{ij}.
有度量就能定義面內的夾角——查一查你的高中課本是怎麼定義向量之間的夾角的。

舉個例子,我想知道平面上很相近兩個像素點(a_1,b_1),(a_2,b_2)之間的距離的平方, 它是這麼算的:
(Delta s)^2=sum^2_{i,j=1}g_{ij}Delta x_iDelta x_j, 其中left{
egin{aligned}
Delta x_1=a_2-a_1\
Delta x_2=b_2-b_1\
end{aligned}
ight.
只要取圖像上各個點g_{ij}=delta_{ij}=
egin{pmatrix}
10\01
end{pmatrix}, 就是勾股定理,就是平面歐氏幾何。

球面幾何是怎樣的呢,如果距離很近的兩個像素點方位角分別是(phi_1,	heta_1),(phi_2,	heta_2),那麼
(Delta s)^2=sum^2_{i,j=1}G_{ij}Delta xi_iDelta xi_j,其中left{
egin{aligned}
Delta xi_1=phi_2-phi_1\
Delta xi_2=	heta_2-	heta_1\
end{aligned}
ight.
這裡G_{ij}=
egin{pmatrix}
sin^2	heta0\01
end{pmatrix}, 這就是球面幾何。

任何攝影都有一個光學中心,相機就是以這個光學中心為球心,將球面上的影像映射到平面的ccd/屏幕上。也就是說,從「真實」的影像到你屏幕上的影像,是一個這樣的映射:
P:(xi_1,xi_2)mapsto(x_1,x_2)
伴隨的度規矩陣映射是G_{ij}mapsto 	ilde{g}_{ij}

這個所謂的	ilde{g}_{ij}的對角元很可能不是1(圖像的伸縮),甚至還有非0的非對角元(圖像的剪切形變),取決於你的P. 這就是你照片邊緣的球被扯成了蛋的原因,不過一般來講,圖像中心的度量矩陣,就剛好是	ilde{g}_{ij}= delta_{ij}, 所以圖片中間的球還是比較端正的。

那題主你想問的問題,用上面的話說,就是:

有沒有辦法通過知道P是什麼並且修正它,使得	ilde{g}_{ij}= g_{ij}=delta_{ij}?

答案是做不到的,因為球面有曲率,而平面(屏幕)沒有,曲率是g_{ij}一階導數和二階導數的函數,我們不可能選擇一個新的P產生一個新的	ilde{g}_{ij}使得一片區域內曲率處處為0

有一款軟體叫PTGui的,做全景的同學可能比較熟悉,其中的小行星效果也僅僅是達成一個比較妥協的投影:	ilde{g}_{ij}= Omega^2g_{ij}=Omega^2delta_{ij}, Omega是隨著位置變化的一個因子,我們稱為共形(conformal)映射.

這裡由於	ilde{g}_{ij}對角而且對角元相等,可以發現圖像各點是沒有剪切畸變的,只是各點放大率不一樣。

值得一提的是,共形映射保圓*,雖然圖像各點放大率不一樣,但一個端正的圓,投影之後肯定還會是一個端正的圓,雖然各點放大率不一樣,但是至少不會是個蛋。這一點可以保證題主的綠球還是端正的。

*註:直線是半徑無窮大的圓,圖中任何直線段都變成了圓的一部分.


看起來像fov過大,調小試試


真正解決的唯一方法只能用球面顯示器,且人眼在球心位置(現在的vr是這個方法吧),不然總存在一個角度觀察正確,另外一個角度(位置)觀察畸變


推薦閱讀:

為什麼會有那麼多類型的 BxDF?
高斯模糊和高斯有什麼關係?
為何遊戲里的3d渲染速度非常快,而三維軟體實時渲染非常慢?
為什麼計算機圖形不用四面體做基本圖元?
如何學習計算機圖形學?

TAG:物理學 | 光學 | CG | 計算機圖形學 |