smallpt: Global Illumination in 99 lines of C++
smallpt: Global Illumination in 99 lines of C++
光線追蹤
正向光線追蹤
正向光線追蹤符合常識:光線從發光物體出發出,“撞擊”到被觀察物體上,經過一系列光線傳輸進入人眼。
因此正向光線追蹤的基本流程可以簡述爲:
- 追蹤從發光體發射的所有光線
- 檢測光線是否“撞擊”物體
- 如果沒有“撞擊”物體,直接拋棄
- 如果撞擊到物體。看反射光線是否直接射入人眼
-如果沒有直接射入人眼,則其可能經過一系列傳輸才射入人眼(間接射入人眼),需要進步判斷
-如果直接射入人眼,則計算當前反射光線的顏色,作爲該撞擊點的顏色
從上述流程不難可以看出,正向光線追蹤需要追所有光線。而在這所有光線中,只有一部分光線會“撞擊”到觀察體,“撞擊”到觀察體的光線也只有部分會射入人眼。因此追蹤所有光線,計算量大且做無用功較多。
逆向光線追蹤介紹
追蹤光線的方向與正向相反:從眼睛處射出光線,追蹤光線射擊到物體後,是否能回到光源。如果能則說明該點被光源照亮,否則物體上該點可能被間接照亮,需要進一步判斷。
逆向光線追蹤爲什麼比正向光線追蹤號好呢?
因爲從圖形學的角度來看
人眼發射出的光線是有限的,而光源發射出的光線是無限的:人眼接收圖像是二維的像素組成的。每個像素記錄着該點的顏色。也即對於每個像素計算其顏色, 代表着該像素上一次光線追蹤的結果。
因此逆向光線追蹤的基本流程可以簡述爲:
FOR 每個像素點 :
構造人眼入射光線Ray
光線追蹤Ray :
計算與光線Ray相交的最近的物體Obj
IF obj == null :
該像素點顏色爲缺省值:全局環境光AmbientColor
Continue
ELSE
看反射光Reflection是否能直接與光源相連(未被其他物體遮擋)
IF 沒有被遮擋
該像素點顏色爲光源顏色在該材質上的作用
ELSE IF 被遮擋
該像素點顏色根據反射光的光線追蹤結果得到。
蒙特卡羅光線追蹤算法
個人理解,不對歡迎指正
蒙特卡洛思想介紹
蒙特卡羅光線追蹤對逆向光線追蹤模型進行改進,其中最大的區別在於把概率模型引入光線追蹤。
- 逆向光線追蹤中物體的表面材質很單一。引入俄羅斯賭盤輪,可以設定漫反射、鏡面反射、甚至折射的概率。豐富表面材質的顯示
- 每個像素點只採樣一條光線計算出的顏色,正確率不高。引入蒙特卡羅可以多次採樣求平均,優化渲染結果。
根據上述分析光線追蹤算法中最重要的步驟可以分解成兩個:
- 射線與多邊形物體的求交判斷(空間劃分kdTree)
- 光線追蹤這個遞歸子算法(包括對各種材質的處理)
下面由於準備材料不夠,我先只主要介紹方面2:
非透明材質
漫反射材質
漫反射材質表現爲表面不規則,因此反射光線的方向無法確定判斷。它朝可能的任意方向反射。
我們假定對於漫反射材質,反射光線的方向範圍可以限定在以撞擊點爲圓心,撞擊點法相爲中心的半圓內。
假設:
入射光線爲Ray(x0, d0)。其中x0表示光線起點 d代表光線方向
入射光線與物體表面的相交點爲x
入射光線與物體表面相交點x出的法向爲n
目標:求反射光線Reflection Ray(x, d1).
1.構建以x點爲中心,n爲一個座標軸的笛卡爾直角座標系
w = n
u=((fabs(w.x)>.1?Vec(0,1,0):Vec(1,0,0)) * w).norm()
v=w * u
u/v/w即組成一個笛卡爾直角座標系
2.將反射光線方向d1分解爲u/v/w表示形式
如下圖所示
d1 = |d1| * cosα * cosθ * u + |d1| * sinα * w + |d1| * cosα * sinθ * v
其中α爲 [0, PI/2]中的隨機數;θ爲[0,2*PI]中的隨機數
這兩個步驟對應smallpt: Global Illumination in 99 lines of C++中代碼片段56-60:
if (obj.refl == DIFF){ // Ideal DIFFUSE reflection
double r1=2*M_PI*erand48(Xi), r2=erand48(Xi), r2s=sqrt(r2);
Vec w=nl, u=((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm(), v=w%u;
Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();
return obj.e + f.mult(radiance(Ray(x,d),depth,Xi));
}
鏡面反射材質
鏡面反射比較簡單,反射光線可假定嚴格按照反射定律來求(入射角定於反射角)
假設:
入射光線爲Ray(x0, d0)。其中x0表示光線起點 d代表光線方向
入射光線與物體表面的相交點爲x
入射光線與物體表面相交點x出的法向爲n
目標:求反射光線Reflection Ray(x, d1).
具體計算步驟如下圖所示
d1 = d0 - n * 2 * (n * d0)
這兩個步驟對應smallpt: Global Illumination in 99 lines of C++中代碼片段61-62:
else if (obj.refl == SPEC) // Ideal SPECULAR reflection
return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi));
透明材質
反射與折射
非透明材質在光線追蹤的過程中,要不是漫反射,要不是鏡面反射。與非透明材質不同,透明材質在光線作用下,反射和折射是同時存在的(全反射除外)。因此在光線追蹤透明材質時,需要同時考慮這兩種發射情況。
折射光線方向的計算:斯涅爾定律
簡單介紹一下斯涅爾定律,它主要是描述清楚了入射角與折射角之間的關係:
n1sinθ1 = n2sinθ2
其中n1/n2是兩個介質的折射率;θ1/θ2分別是入射角/折射角
折射光線方向計算:
假設:
入射光線爲Ray(x0, d0)。其中x0表示光線起點 d代表光線方向
入射光線與物體表面的相交點爲x
入射光線與物體表面相交點x出的法向爲n
兩種介質的折射率分別爲n1、n2
目標:求折射光線Reflection Ray(x, d1).
這個步驟對應smallpt: Global Illumination in 99 lines of C++中代碼片段68:
Vec tdir = (r.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm();
考慮全反射:入射角大於某一臨界角θc(光線遠離法線)時,折射光線將會消失,所有的入射光線將被反射。
當折射角等於90°時,入射角即達到臨界角。
這部分步驟對應smallpt: Global Illumination in 99 lines of C++中代碼片段63-67:
Ray reflRay(x, r.d-n*2*n.dot(r.d)); // Ideal dielectric REFRACTION
bool into = n.dot(nl)>0; // Ray from outside going in?
double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t;
if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0) // Total internal reflection 全反射
return obj.e + f.mult(radiance(reflRay,depth,Xi));
折射光線與反射光線混合:菲涅耳公式
這部分步驟對應smallpt: Global Illumination in 99 lines of C++中代碼片段69-73
double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n));
double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P);
return obj.e + f.mult(depth>2 ? (erand48(Xi)<P ? // Russian roulette 俄羅斯賭盤
radiance(reflRay,depth,Xi)*RP:radiance(Ray(x,tdir),depth,Xi)*TP) :
radiance(reflRay,depth,Xi)*Re+radiance(Ray(x,tdir),depth,Xi)*Tr);
待更新
參考:
1.smallpt: Global Illumination in 99 lines of C++
2.scratchapixel: Introduction to Ray Tracing: a Simple Method for Creating 3D Images
3.scratchapixel:Rendering an Image of a 3D Scene: A Light Simulator
4.百度百科:斯涅爾定律、菲涅耳公式
5.知乎:如何用 C++ 實現光線跟蹤軟渲染器?