計算機圖形學的一個基本操作是渲染3D物體,例如由很多個幾何物體組成的場景或模型,然后再從某一個角度觀察3D模型並生成對應的2D圖片。從根本上來講,渲染是輸入一些物體並輸出一個矩陣的像素,因此渲染要考慮每一個物體是如何影響每一個像素的。通常有兩種順序,分別是以物體為序的渲染(object-order rendering)與以圖片為序的渲染(image-order rendering)。其中物體為序指的是遍歷每一個物體,考慮每一個物體對所有像素的影響。圖片為序指的是遍歷每一個像素,考慮每一個像素受所有物體的影響。這兩種方法產生的最后結果是類似的,但是他們有一些不同的優點與缺陷,會在第八章進行更多討論。就一個前瞻而言,圖片為序的渲染更容易實現與更改適用范圍來產生不同的效果,但是需要消耗更多的運行時間。我們在這里先介紹圖片為序渲染的光線追蹤算法,相比起物體為序的渲染來講需要更少的數學工具。
1. 基礎的光線追蹤算法
可見此文的3.2部分。流程為根據攝像機的幾何屬性來計算每個像素光線的起始點和方向,其次尋找在該光線上第一個相交(最近)的物體,最后根據該物體的顏色、法向量和光照等信息計算出像素的顏色。
2. 透視
攝影與計算機圖形學很多時候都在處理將三維物體轉為二維圖片的過程,其中除了會造成扭曲或變形的特殊方法外,我們通常使用線形透視的方法來直接處理。常用的有兩種,分別是
-平行投影,即所有點到屏幕的投影方向是一致的,可以保持原物體的大小比例等信息,常用於工程圖等
-透視投影,即所有點都投影至一個視點(View Point),中間取屏幕的一個截面,原理接近於照相機的小孔成像。這個方法的結果與近大遠小的人眼觀察效果類似.
就透視方法而言可以見各類美術科普,本鏈接有一個較為直觀簡單的初步介紹。此外,投影方向和被投影的屏幕之間存在正交的(Orthographic)或者斜的(Oblique)關系。相較美術復雜嚴謹的透視構圖而言,計算機圖形學只要在算法上維持透視的正確性就可以很輕松的生成正確透視的二維圖像。
3. 計算視線
可以簡單講射線表示為參數向量的形式,即\(p(t)=e+t(s-e)\),具體如下。
其中\(e\)為相機的點位置,相機的坐標系可利用上右后的右手坐標系來進行表示\(\{u,v,w\}\)並如后文方法計算出\(s\)。
3.1 平行正交投影
可以簡單讓所有視線方向為攝像機的正面方向,即\(-w=s-e\)。接下來在攝像機處構成一個\((u,v)\)垂直於\(-w\)的一個平面,將物體投影至平面上。本文使用\(l<0<r,b<0<t\)的橫向與縱向位移為限制,位移單位為\(u,v\)的單位向量。我們的目的是將最終成像的大小\((n_x,n_y)\)點陣圖像素\((i,j)\)轉換到這個投影平面坐標\((a,b)\)上。如果將像素點放在對應小方格的中間而不是左下角,則可得\(a=l+(r-l)/n_x*(i+0.5),b=(t-b)/n_y*(j+0.5)\),其中\(0.5\)是像素點在方格中間導致的位移,而\((r-l)/n_x\)則是橫向的單位方格長。在計算出平面坐標\((a,b)\)后,可以得到投影點坐標為\(e+au+bv\),注意這里的\(u,v\)默認為單位向量。
3.2 透視投影
透視平面在攝像機前構成一個投影平面,其中攝像機到投影平面的距離為\(d\),被稱為圖片平面距離(Image Plane Distance)或者焦距(Focal Length)。與之前一樣通過像素位置\((i,j)\)計算投影平面坐標\((a,b)\),則視線方向為\((e-dw+au+bv)-e=-dw+au+bv\),視線原點為\(e\)。
4. 視線與物體相交
我們從上一節已經得到了視線的公式\(e+td\),其中\(e\)是起始點,\(d\)是方向向量。我們通常認為\(t\in[0,+\infty)\),考慮相交問題時需要考慮是否存在最小的\(t\)使得射線接觸物體。雖然很多操作時我們通常考慮與三角形進行接觸,但是在此部分也會討論一些特殊圖形的特殊處理方法。
4.1 與球體相交
籠統來講如果有三維隱式函數表示的Surface\(f(p)=0\),我們可以解方程\(f(p(t))=f(e+td)=0\)。就球體來講我們有\(f(p)=(x-x_c)^2+(y-y_c)^2+(z-z_c)^2-R^2=0\)或\((p-c)\cdot(p-c)-R^2=0\),代入視線函數\(p(t)=e+td\)得到\((e+td-c)\cdot(e+td-c)-R^2=(d\cdot d)t^2+2d\cdot(e-c)t+(e-c)\cdot(e-c)-R^2=0\),可以解\(t\)的二元一次方程得解。
4.2 與三角形相交
視線與三角形相交問題(Ray-Triangle Intersection)有很多方法,這里使用基於重心坐標(Barycentric coordinate)的Möller-Trumbore(MT)算法,主要因為其需要的內存空間較小且速度較快。此鏈接附帶另外兩個方法,
首先我們將三角形寫作兩個向量的參數平面即\(f(u,v)\),可得相交點為\(e+td=f(u,v)\)。因為\(x,y,z\)三個方向的相等帶來了三個公式,以及我們擁有\(u,v,t\)三個未知數,存在利用計算方法直接求解的可能。假設三角形三點為\(a,b,c\),則可有\(e+td=\alpha+\beta(b-a)+\gamma(c-a)\)。如果\(\beta,\gamma>0,\beta+\gamma<1\)則可得交點在三角形內,否則在三角形外。如果無解可能是三角形三點共線(degenerate)或者射線與三角形平面平行且不重合。MT算法通過利用線性代數中的克萊姆法則(Cramer Rule)來直接得解。克萊姆法則計算上可以理解為行列式的比率,分母是原線性變換矩陣的行列式,分子是將參數所對應的列改為結果向量。例如\(\beta\)是對應第一列,則將第一列改為結果的\([x_a-x_e,y_a-y_e,z_a-z_e]\)。幾何上稍顯復雜,建議觀看此視頻。其方法是對於向量\((\beta,\gamma,t)\)來講,可以將\(\gamma\)值看做\((\beta,\gamma,t),j,k\)三個向量組成的平行六面體體積。經過\(A\)的矩陣變換后\((\beta,\gamma,t)\)變成了結果向量的\((x_a-x_e,y_a-y_e,z_a-z_e)\),另外兩個\(j,k\)則分別變為變換矩陣的第二列和第三列(線性變換的基礎)。通過行列式代表體積變換比率的原則,可得“原體積=變換后體積/體積改變比率”。其中\(\beta\)的值為原體積,\(|A|\)為變換矩陣的行列式即體積改變比率,公式就講清楚了。這里需要注意的是,如果\(|A|=0\),這代表視線方向與三角形的兩個向量組成的行列式值為0,幾何意義上表示由\((i,j,k)\)組成的單位立方體體積經過矩陣\(A\)變換后的體積縮小到了\(0\),進而代表視線與三角形共面甚至共線,此情況下直接判斷為不相交即可(即使相交也不看不到,三角形厚度為0)。
MT算法在計算時使用克萊姆法則而不是更快的高斯消元主要是因為通常我們不需要知道全部三個\(\beta,\gamma,t\)值就能判定三角形與視線是否相交。就實際操作時,我們會判定\(t\)是否在視線限制范圍內的\([t_0,t_1]\)種,檢測\(\gamma\)是否在\([0,1]\)之間,最后再檢測\(\beta>0,\beta+\gamma<=1\)。因為提前篩選的原因,很有可能在第一步和第二步克萊姆法則就已經停止運算返回結果,不需要全部的高斯消元來計算三個值。具體見下圖。
4.3 與多邊形相交
對於一個包含點\(p_1,p_2,\cdots,p_n\)以及法向量\(n\)的多邊形來講,有隱式函數\((p-p_1)\cdot n=0\)。連立視線函數\(p=e+td\)可得\(t=\frac{(p_1-e)\cdot n}{d\cdot n}\)。通過\(t\)可以計算出點\(p=e+td\),其次檢查\(P\)是否在多邊形內。一個檢查方法是將多邊形投影至\(xy,yz,xz\)平面的任何一個上,然后自點\(P\)創造一個任意射線(通常是沿着標准xyz方向中的一個),如果射線交多邊形的投影個數為奇數則點\(P\)在多邊形內。選取三個平面中的較好的一個是為了避免多邊形在某個單一平面的投影為一條線的情況。另外一個方法則是將多邊形切割為三角形再進行判定,更接近實際操作的方法。
4.4 與多個物體相交
一個簡單的方法是依次檢測每一個物體,並選取\(t\)值最小,即距離最近的物體。
5. 着色(Shading)
在找到視線與物體的交點后,像素值會依據不同的着色模型來被計算出來。此處記錄兩個常用的着色模型,更多的模型會在第十單元進行討論。大部分的着色模型設計是模仿光的反射過程,即被光源照射的表面將一部分的光反射至照相機的過程。通常使用三個關鍵參數,分別是朝向光源的向量\(l\),朝向相機的向量\(v\)與表面法向量\(n\)。就不同模型而言可能還有一些其他參數,例如表面的顏色、光澤度等。
5.1 Lambertian Shading
Lambertian Shading僅與光源方向及表面法方向(\(l,n\))相關,兩者之間的cos夾角決定了反射光的強度。公式為\(L=k_d I max(0,n\cdot l)\),意義上為"像素值=漫反射系數光線強度夾角光強保留系數"。這個方法采取了簡單的漫反射方法,也被稱為Diffuse Shading。
5.2 Blinn-Phong Shading
Lambertian Shading的結果與視線方向無關,但是在現實生活中不同的觀察角度會有不同的光照效果,球體也有鏡面式反射的位置隨着視角移動而變化的效果(Specular reflection)。因此很多着色模型會在Lambertian shading的漫反射部分(Diffuse Component)外額外增加一個鏡面反射部分(Specular Component)。
Blinn-Phong Shading是由兩人前后提出的着色模型,主要增加當光線和視線圍繞法線越接近對稱則擁有更高亮度的特性,這一特性是符合常識的(對稱則光反射后直接進入人眼或相機)。實現的方法是從光線與視線兩個向量\(v,l\)中取中間方向向量\(h\)。如果中間向量\(h\)與法向量\(n\)更為接近,則說明視線與光線越對稱。公式與示意圖分別如下,此處\(k_s\)為鏡面反射系數,\(p\)被稱為Phong exponent,主要讓鏡面反射部分的值下降得更快,使得對稱部分呈高光的白色,其他部分則快速降低鏡面反射部分的像素值。如果\(p\)值很小,則很有可能出現鏡面反射部分大於漫反射部分的情況,導致整個物體呈白色。
5.3 Ambient Shading(環境光照)
為了避免沒有被光照或者角度極差的表面在成像時完全為黑色,實際操作時可能會對所有表面增加一個固定值的環境光照。因此上文的Blinn-Phong Shading可以被修改為以下情況。需要注意的是\(k_a,k_d,k_s\)為三個系數,但是都是以顏色或者其他屬性表示的。
5.4 多點光源
在處理多點光源的時候有很多模型,一個可用的模型是簡單的將各個光源的值相加得解,具體如下。
6. 光線追蹤程序
在剛才的章節內已經介紹了如何計算出視線,如何檢測視線與物體相交,以及如何基於Blinn-Phong着色模型計算交點對像素的影響。在這三者之下,可以寫出以下的簡單光線追蹤程序的偽代碼。
6.1 OOP設計的光線追蹤程序
在實際操作的時候,可以設計一個abstract class稱為surface,然后各類幾何物體\(triangle, sphere, polygon, group\)等設置為子類。這樣大部分通用的計算方法都可以在surface類編寫,減少冗余。一些可用的method包括檢查視線是否接觸surface的hit函數以及得到表面外圍最大碰撞箱的boudning-box函數。還有一些可以使用的大類包括Material或者Texture等,具體設計與使用可以參考其他章節以及一些現實的項目。
7. 陰影(Shadows)
光線追蹤程序的陰影較為容易計算。如果檢測點\(P\)是否在陰影,只需要自點\(P\)向光源處作一條射線進行物體碰撞檢測即可。如果碰到了物體則說明無法從點\(P\)行至光源,反向說明\(P\)在陰影中。我們常將該射線稱為shadow rays(陰影線)。這里簡單使用全局光造成的陰影作為例子,即假設光源(比如太陽)在極遠處,則所有點的光照來源都一致。具體算法如下。注意這個算法\(e+td\)形容的是從照相機來的視線,而\(p+sl\)說的是陰影線。其中\(s\)取一個小的正\(\epsilon\)為起始范圍是為了避免因為計算精度問題導致計算陰影線碰撞時把起始點點\(P\)平面自己算了進去。
理想鏡面反射(Ideal Specular Reflection)
對於鏡面地板或者簡單鏡面反射的水面來講,光線追蹤都能很簡單的添加理想鏡面反射的功能。第一步是計算視線與反射面的反射角度\(r=d-2(d\cdot n)n\),示意圖如下
其次,我們使用一個遞歸函數\(raycolor(p+sr,\epsilon,\infty)\)去尋找反射線的顏色,具體實現方法多種多樣,一個簡單的方法可以是在有限次數的反射內加權計算反射線碰撞到的表面顏色,並注意每次折射之后光線的能量損失。這么一來反射導致的光強則為如下。
最終的效果見下圖