GAMES101蒙特卡洛光线追踪及Assignment7
文章目錄
- 前言
- 蒙特卡洛路徑追蹤
- 蒙特卡洛積分
- 路徑追蹤
- Whitted-Style光線追蹤的局限性
- 渲染方程
- 直接光照
- 間接光照
- 兩個問題
- 優(yōu)化
- 總結(jié)
- Assignment 7
- 準備工作
- 實現(xiàn)過程
- 最終結(jié)果
- 提高部分
- 參考
前言
本篇博客作為Games101關(guān)于Ray Tracing部分以及Assignment7路徑追蹤的復習總結(jié)。
蒙特卡洛路徑追蹤
蒙特卡洛積分
在此之前先復習一下概率密度函數(shù)(PDF, Probability Density Functions),可以理解為取某一隨機變量x的概率是多少的函數(shù)p(x)p(x)p(x)
PDF具有如下特點
- $ p(x) >= 0 \ and \int{p(x)}=1$
- E[X]=∫xp(x)dxE[X]=\int{xp(x)}dxE[X]=∫xp(x)dx
對于復雜函數(shù)f(x)f(x)f(x)求定積分,用牛頓萊布尼茨公式十分困難,這時候需要用到另一種積分方法,也就是下面的蒙特卡洛積分(Monte Carlo Integration)
∫f(x)dx=1N∑i=1Nf(Xi)p(Xi)Xi~p(x)\int{f(x)}dx = \frac{1}{N}\sum_{i=1}^{N}{\frac{f(X_i)}{p(X_i)}}\ \ \ \ \ \ \ X_i \sim p(x) ∫f(x)dx=N1?i=1∑N?p(Xi?)f(Xi?)????????Xi?~p(x)
這里的做法可以理解為按p(x)p(x)p(x)的概率取隨機變量XiX_iXi?,在圖形學里也就是隨機采樣函數(shù)值再累加做平均。
一些需要注意的地方:
- 采樣點N越多,方差越小
- 在x上采樣,也是在x上求積分
路徑追蹤
路徑追蹤(Path Tracing)根據(jù)渲染方程實現(xiàn)全局光照,或者說基于物理的渲染。個人理解叫追蹤的原因是因為光線本來是打到物體上再多次彈射到人眼所以人才能看見東西,追蹤就是逆著這個路徑,從眼睛“射出”光線,“所見即所得”。
Whitted-Style光線追蹤的局限性
Whitted-style ray tracing是一種理想化模型,存在一些問題,光線只有反射或折射,在漫反射材質(zhì)停止彈射,沒有漫反射,因此Whitted-style ray tracing渲染出來的效果只有光源和直接光照,如下圖左側(cè)所示。我們理應看到的效果是下圖右側(cè)這種,就像是照片拍出來的一樣,需要實現(xiàn)這樣真實的效果就需要用到路徑追蹤。
渲染方程
根據(jù)輻射度量學,我們一通操作最終求得渲染方程,即在物理意義上得出的正確的能夠描述我們現(xiàn)實生活所見的方程。
Lo(p,ωo)=Le(p,ωo)+∫ΩLi(p,ωi)fr(p,ωi,ωo),(n?ωi)dωiL_o(p,\omega_o)=L_e(p,\omega_o)+\int_{\Omega}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o),(n\cdot\omega_i)d\omega_i Lo?(p,ωo?)=Le?(p,ωo?)+∫Ω?Li?(p,ωi?)fr?(p,ωi?,ωo?),(n?ωi?)dωi?
某著色點到相機的radiance由其本身發(fā)光產(chǎn)生的部分,和從所有立體角接受到的radiance并反射到相機這個一定角度的部分組成。
為了方便計算和統(tǒng)一,這里將著色點本身產(chǎn)生的Le(p,ωo)L_e(p,\omega_o)Le?(p,ωo?)省略掉,我們更關(guān)注如下這個渲染方程(或者說反射方程)。
Lo(p,ωo)=∫ΩLi(p,ωi)fr(p,ωi,ωo),(n?ωi)dωiL_o(p,\omega_o)=\int_{\Omega}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o),(n\cdot\omega_i)d\omega_i Lo?(p,ωo?)=∫Ω?Li?(p,ωi?)fr?(p,ωi?,ωo?),(n?ωi?)dωi?
根據(jù)蒙特卡洛積分可以得到
Lo(p,ωo)≈1N∑i=1NLi(p,ωi)fr(p,ωi,ωo)(n?ωi)p(ωi)L_o(p,\omega_o)\approx\frac{1}{N}\sum_{i=1}^{N}\frac{L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)(n\cdot\omega_i)}{p(\omega_i)} Lo?(p,ωo?)≈N1?i=1∑N?p(ωi?)Li?(p,ωi?)fr?(p,ωi?,ωo?)(n?ωi?)?
這個方程可以理解為按p(ωi)p(\omega_i)p(ωi?)的概率隨機選取N個方向ωi\omega_iωi?采樣,再累加求平均。
注意,這里的ωo\omega_oωo?和ωi\omega_iωi?都是由著色點向外的
直接光照
僅在直接光照下,考慮計算radiance的場景如下
由此可以得到著色方程的shade偽代碼
shade(p, wo)Randomly choose N directions wi~pdfLo = 0.0For each wiTrace a ray r(p, wi)If ray r hit the light // 彈射的光線必須要能打到光源,這個過程中不能被其他物體阻擋Lo += (1 / N) * L_i * f_r * cosine / pdf(wi)Return Lo其中p是著色點,wo是入射光線,wi是出射光線,r是由眼睛穿過需要渲染的像素到實際著色點的光線。
間接光照
如果路徑追蹤經(jīng)過一次彈射后沒有打到光源而是打到物體,就構(gòu)成了間接光照。我們考慮如下這個場景
P接收到的radiance是由Q反射光源得到的,因此為了求點P反射的光,我們需要先求點Q反射出的光。顯然,這里用到了遞歸的思路,將上述shade偽代碼稍作修改即可完成
shade(p, wo)Randomly choose N directions wi~pdfLo = 0.0For each wiTrace a ray r(p, wi)If ray r hit the lightLo += (1 / N) * L_i * f_r * cosine / pdf(wi)Else If ray r hit an object at qLo += (1 / N) * shade(q, -wi) * f_r * cosine / pdf(wi)Return Lo可以看到只是增加了一個判斷條件,判斷從點p反射出的的光線wi是射到了光源還是其他物體,如果是光源直接加,如果是其他物體則遞歸先求L_i,也就是shade(q,-wi)。
注意,由于shade的參數(shù)要求光線方向向外,所以對于點Q,需要將wi取反。
兩個問題
上述shade偽代碼還存在兩個問題
- 每次都會采樣N個方向,最終導致指數(shù)爆炸
- 遞歸沒有出口
對于第一個問題,可以用下圖形象表示
在計算間接光照時,由于每次都要采樣N個點,最終導致NxN^xNx的指數(shù)增長。但有一個數(shù)是可以避免這種情況,那就是N取1。
在采樣點每次采樣只取1個方向,我們的shade偽代碼又有了如下改動
shade(p, wo)Randomly choose ONE direction wi~pdf(w)Trace a ray r(p, wi)If ray r hit the lightReturn L_i * f_r * cosine / pdf(wi)Else If ray r hit an object at qReturn shade(q, -wi) * f_r * cosine / pdf(wi)由前文的蒙特卡洛積分我們知道,N取越大越等接近原定積分的值,N取越小結(jié)果的噪聲或者說偏差就會越大。我們這里取N為1,可想而知會有很大偏差。為了解決這個問題,可以采取對同一像素多次采樣的方法,根據(jù)需要設置對每個像素采樣的次數(shù)(spp, samples per pixel)。實現(xiàn)的效果如下圖所示,可以看到間接光照每次采樣的彈射結(jié)果雖然都不一樣,但多次采樣求平均后就可以有效減小噪聲。
上述的采樣方法用偽代碼描述如下,與shade偽代碼類似
ray_generation(camPos, pixel)Uniformly choose N sample positions within the pixelpixel_radiance = 0.0For each sample in the pixelShoot a ray r(camPos, cam_to_sample)If ray r hit the scene at ppixel_radiance += 1 / N * shade(p, sample_to_cam)Return pixel_radiance現(xiàn)在我們來解決第二個問題,這個遞歸算法沒有出口所以會一直持續(xù)下去,因此我們需要認為設置一個出口。
這里采用的方法是俄羅斯輪盤賭。設定一個值Russian Roulette (RR),光線每次彈射前需先與RR“賭博”,如果成功則繼續(xù)彈射,否則停止。我們可以設在概率P_RR下,將射出光線返回的渲染方程結(jié)果Lo除以P_RR,那么將會有1-P_RR的概率停止彈射直接返回0。在此假設下,我們可以得到期望值E,是與我們期望得到的Lo相同的。
E=P?(Lo/P)+(1?P)?0=LoE=P*(Lo/P)+(1-P)*0=Lo E=P?(Lo/P)+(1?P)?0=Lo
由此,我們的shade偽代碼再次改動如下
優(yōu)化
到此,我們終于得到了一個正確的路徑追蹤算法,的的確確能正確渲染出一個像素,但是目前這種做法的效率其實并不高。在計算直接光照時,如果我們按照上面得偽代碼計算直接光照部分,那么每次只會隨機射出一根光線,實際上很難打到光源上,計算效率低下。
為了提高成功采樣到直接光照的概率,我們可以對直接光照計算的方法進一步修改,在彈射點進行多次采樣。與間接光照遞歸計算相比,因為直接光照只會彈射一次到光源,所以這樣并不會造成指數(shù)爆炸。
如上圖所示,隨著著色點采樣數(shù)的提高,成功采樣的幾率自然會提高。但是當光源很小時,如果我們繼續(xù)采用平均采樣,那么彈射的數(shù)跟光線中可能只有少數(shù)幾根能打到光源上,其他采樣光線可以說都是被浪費掉了,這同樣是我們不希望看到的。
如果能對更加確定的東西采樣,那么這個問題自然迎刃而解??紤]我們是想要找到彈射點能打到光源的有效光線,所以反過來考慮,我們對光源進行采樣。并且實際上,蒙特卡洛積分也是允許我們自定義采樣方法的。
Lo(p,ωo)=∫ΩLi(p,ωi)fr(p,ωi,ωo)(n?ωi)dωiL_o(p,\omega_o)=\int_{\Omega}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)(n*\omega_i)d\omega_i Lo?(p,ωo?)=∫Ω?Li?(p,ωi?)fr?(p,ωi?,ωo?)(n?ωi?)dωi?
上面是我們在前文我們得到的渲染方程,可以看到我們的積分目標是dωid\omega_idωi?,可以解釋為我們是從著色點出發(fā)考慮這個積分的。如果我們對光源進行采樣,積分對象自然也需要變化,所以我們需要找到dωid\omega_idωi?和dAdAdA之前的關(guān)系。下圖很好展現(xiàn)了dωid\omega_idωi?和dAdAdA之前的關(guān)系。
回顧以前對立體角ω\omegaω的定義
dω=dAr2d\omega = \frac{dA}{r^2} dω=r2dA?
單位立體角就是單位面積與距離平方的比值。因此在這里我們可以類比得出相似的關(guān)系,類似Bllin-Phong模型,由光源投射出的光線方向與半球面有一個夾角,所以這里的A=Acosθ′A=Acos\theta'A=Acosθ′,而r自然是兩點之間的距離r=x′?xr=x'-xr=x′?x。由此,我們得到dωid\omega_idωi?和dAdAdA之前的關(guān)系
dω=dAcosθ′∣∣x′?x∣∣2cosθ′=nd\omega = \frac{dAcos\theta'}{||x'-x||^2} \ \ \ \ \ cos\theta'=n dω=∣∣x′?x∣∣2dAcosθ′??????cosθ′=n
將上式代入渲染方程,我們就可以得到按光源采樣的新蒙特卡洛積分形式
Lo(p,ωo)=∫ΩLi(p,ωi)fr(p,ωi,ωo)cosθdωi=∫ΩLi(p,ωi)fr(p,ωi,ωo)cosθcosθ′∣∣x′?x∣∣2dA≈1N∑i=1NLi(p,ωi)fr(p,ωi,ωo)cosθcosθ′∣∣x′?x∣∣2p(A)其中cosθ=n?dωcosθ′=n?(?dω)\begin{aligned} L_o(p,\omega_o) &=\int_{\Omega}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)cos\theta d\omega_i\\ &=\int_{\Omega}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)cos\theta\frac{cos\theta'}{||x'-x||^2}dA\\ &\approx\frac{1}{N}\sum_{i=1}^{N}\frac{L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)cos\theta\frac{cos\theta'}{||x'-x||^2}}{p(A)}\\ 其中&cos\theta=n\cdot d\omega \ \ \ \ \ cos\theta'=n\cdot(-d\omega)\\ \end{aligned} Lo?(p,ωo?)其中?=∫Ω?Li?(p,ωi?)fr?(p,ωi?,ωo?)cosθdωi?=∫Ω?Li?(p,ωi?)fr?(p,ωi?,ωo?)cosθ∣∣x′?x∣∣2cosθ′?dA≈N1?i=1∑N?p(A)Li?(p,ωi?)fr?(p,ωi?,ωo?)cosθ∣∣x′?x∣∣2cosθ′??cosθ=n?dω?????cosθ′=n?(?dω)?
對于上式,由pdf性質(zhì)我們知道按p(A)p(A)p(A)對面積積分的結(jié)果應該是1(∫p(A)dA=1\int p(A)dA=1∫p(A)dA=1),自然p(A)=1Ap(A)=\frac{1}{A}p(A)=A1?。
針對直接光照部分,按面積對光源采樣的shade偽代碼如下所示
shade(p, wo)# Contribution from the light source.Uniformly sample the light at x’ (pdf_light = 1 / A)L_dir = L_i * f_r * cos θ * cos θ’ / |x’ - p|^2 / pdf_light# Contribution from other reflectors.L_indir = 0.0Test Russian Roulette with probability P_RRUniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)Trace a ray r(p, wi)If ray r hit a non-emitting object at qL_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RRReturn L_dir + L_indir除此之外,還有一個遺漏了的問題,那就是在光源上采樣的光線必須得能射到著色點上,也就是說著色點ppp和光源x′x'x′之間不能有物體遮擋。
對上述偽代碼稍作修改,判斷一下是否有物體遮擋
shade(p, wo)# Contribution from the light source.Uniformly sample the light at x’ (pdf_light = 1 / A)Shoot a ray from p to x’If the ray is not blocked in the middle // 判斷是否有物體遮擋L_dir = L_i * f_r * cos θ * cos θ’ / |x’ - p|^2 / pdf_light# Contribution from other reflectors.L_indir = 0.0Test Russian Roulette with probability P_RRUniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)Trace a ray r(p, wi)If ray r hit a non-emitting object at qL_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RRReturn L_dir + L_indir自此,我們終于得出了正確的,高效的渲染方式。
總結(jié)
為了盡可能展現(xiàn)我們看到的現(xiàn)實世界,實際上也就是渲染一張圖片,再根本點就是渲染每一個像素。對每個像素進行多次采樣,根據(jù)路徑追蹤得到的渲染方程計算每一次采樣結(jié)果再累加平均,得到該像素理想的渲染結(jié)果。
對于渲染方程,是根據(jù)路徑追蹤得到的基于物理的渲染。與最初我們做的工作相比,之前是簡單粗暴假設光線由半球均勻隨機采樣得到,存在遞歸指數(shù)爆炸,沒有出口,效率低下等問題,而現(xiàn)在我們考慮radiance是有兩部分組成,分別采用不同的方法計算。
- 直接光照,對光源按面積進行采樣
- 間接光照,按半球隨機采樣一次,并且采用RR控制遞歸深度
Assignment 7
作業(yè)7要求我們用代碼實現(xiàn)路徑追蹤算法。在此之前需要用到多個以前作業(yè)實現(xiàn)的函數(shù),下面先貼出這些函數(shù)的代碼,附有詳細注解和函數(shù)作用講解。
準備工作
Triangle::getIntersection in Triangle.hpp
判斷該光線是否與三角形相交并獲得進出時間和相交點的坐標、法線等屬性,返回包含這些屬性的結(jié)構(gòu)體Intersection
IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp
判斷光線ray是否與某一包圍盒是否與相交,用于bvh遞歸對每個劃分節(jié)點進行判斷
getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp
加速結(jié)構(gòu),用于計算光線ray與物體相交的結(jié)果,可只對部分物體判斷而不用遍歷所有物體。通過bvh劃分objects并遞歸判斷,獲得最小的distance也就是時間,得到最近的射線求交結(jié)果
實現(xiàn)過程
在深入理解并正確實現(xiàn)上述代碼以后,我們再來看一下本次作業(yè)框架提供的用于實現(xiàn)路徑追蹤算法的函數(shù)。
-
castRay(const Ray ray, int depth)in Scene.cpp:
在其中實現(xiàn) Path Trac-ing 算法,可能用到的函數(shù)有: -
intersect(const Ray ray)in Scene.cpp:
求一條光線與場景的交點 -
sampleLight(Intersection pos, float pdf) in Scene.cpp:
在場景的所有光源上按面積 uniform 地 sample 一個點,并計算該 sample 的概率密度 -
sample(const Vector3f wi, const Vector3f N) in Material.cpp:
按照該材質(zhì)的性質(zhì),給定入射方向與法向量,用某種分布采樣一個出射方向 -
pdf(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp:
給定一對入射、出射方向與法向量,計算 sample 方法得到該出射方向的概率密度 -
eval(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp:
給定一對入射、出射方向與法向量,計算這種情況下的 f_r 值除此之外,可能用到的變量有:
-
RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率
除此之外,本次作業(yè)由于框架原因所以對原偽代碼做了一些修改(主要是wo定義與課程內(nèi)容相反,由像素指向著色點,而不再是由著色點向外)
shade(p, wo)sampleLight(inter, pdf_light)Get x, ws, NN, emit from interShoot a ray from p to xIf the ray is not blocked in the middleL_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws, NN) / |x-p|^2 / pdf_lightL_indir = 0.0Test Russian Roulette with probability RussianRoulettewi = sample(wo, N)Trace a ray r(p, wi)If ray r hit a non-emitting object at qL_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N) / pdf(wo, wi, N) / RussianRouletteReturn L_dir + L_indir根據(jù)上述偽代碼我們可以得到下面這個圖,方便幫助我們理解并寫代碼
由于在前文學習過程中我們簡化了渲染方程,忽略了物體本身發(fā)光的部分,所以如果按上述偽代碼實現(xiàn)得到的渲染圖中光源部分會是黑色的。很簡單,如果彈射光線遇到發(fā)光的物體則直接返回,我們補上該部分即可得到正確的渲染圖。
最終實現(xiàn)的castRay函數(shù)如下,附有詳細注解
// Implementation of Path Tracing Vector3f Scene::castRay(const Ray &ray, int depth) const {// TO DO Implement Path Tracing Algorithm here// 計算直接光照部分Vector3f L_dir (0, 0, 0);// 從像素射出光線作為著色點的入射光線,獲得渲染方程中入射光線與著色點相交的各項參數(shù)Intersection inter_obj = intersect(ray);if (!inter_obj.happened) // 需先判斷從像素采樣的該光纖是否有射到物體return L_dir;if (inter_obj.m->hasEmission()) // 如果追蹤到光源則直接返回光源,保證光源是亮的return inter_obj.m->getEmission();Vector3f p = inter_obj.coords;Vector3f N = inter_obj.normal.normalized(); // 法線需要計算cos,所以需要單位化Vector3f wo = ray.direction; // 由于框架原因,這里的wo為像素到著色點的方向,與著色點向外的方向相反// 計算反射光線與光源相交,為提高采樣效率所以從光源出發(fā)采樣Intersection inter_light;float pdf_light;sampleLight(inter_light, pdf_light); // 對光源按面積采樣,直接填入默認參數(shù),得到著色點與光源的交點Vector3f x = inter_light.coords;Vector3f ws = (x - p).normalized(); // 從物體指向光源// 從著色點射出光線,與著色點反射的光線比較,判斷著色點反射的光線是否能射到光源Ray objTolight(p, ws);float d1 = (x - p).norm(); // 著色點與光源距離float d2 = intersect(objTolight).distance; // 著色點與相交物體距離// 計算L_dir直接光照if (d2 - d1 > -0.001) { // 做差判斷浮點數(shù)是否相等Vector3f eval = inter_obj.m->eval(wo, ws, N); // 注意參數(shù)的方向,需要入射方向、出射方向Vector3f emit = inter_light.emit; // 光源投射到著色點的光線的radianceVector3f NN = inter_light.normal.normalized();float cos_theta_obj = dotProduct(N, ws);float cos_theta_light = dotProduct(NN, -ws); // 這里要取負號,因為ws是從著色點指向光源,與NN夾角大于90度為負L_dir = emit * eval * cos_theta_obj * cos_theta_light / std::pow(d1, 2) / pdf_light;}// 遞歸計算間接光照部分Vector3f L_indir (0, 0, 0);float P_RR = get_random_float(); // 采用俄羅斯輪盤賭,避免無窮遞歸,最終數(shù)學期望一樣if (P_RR < RussianRoulette) {Vector3f wi = inter_obj.m->sample(wo, N).normalized(); // 對著色點隨機采樣出射光線Ray ray_objToobj(p, wi); // 根據(jù)采樣獲得從著色點射向其他物體的光線Intersection inter = intersect(ray_objToobj);if (inter.happened && !inter.m->hasEmission()) { // 如果有交點且交點材質(zhì)不發(fā)光Vector3f eval = inter_obj.m->eval(wo, wi, N); // 計算著色點的eval,而不是反射光線與其他物體交點的float pdf = inter_obj.m->pdf(wo, wi, N); // 計算著色點采樣的pdffloat cos_theta = dotProduct(wi, N);L_indir = castRay(ray_objToobj, depth + 1) * eval * cos_theta / pdf / RussianRoulette;}}return L_dir + L_indir; }在實現(xiàn)過程中有幾點值得注意
- 在判斷直接光照彈射的光線是否有遮擋的時候,需要用浮點數(shù)比較著色點到光源的距離d1和著色點彈射光線與最近相交物體的距離d2是否相等。判斷浮點數(shù)是否相等我們可以采用做差的方法,由于d1>=d2,所以需要d2-d1>=EPSION,我在這里的EPSION取值為-0.001,能夠滿足判斷要求。
- 對于判斷光線是否與某包圍盒相交的函數(shù)IntersectP,在上課時老師講到圖形學里并不會太關(guān)注邊界情況,所以t_enter<或者<=t_eixt都是沒問題的,但是在本次實驗中我們需要將邊界條件設為小于等于,否則會渲染出的圖片有部分區(qū)域是黑色的。
最終結(jié)果
設置不同的spp,能夠得到不同的渲染效果。ssp越大,自然渲染的效果越好,花費的時間也更長。下面給出不同spp渲染的結(jié)果
spp=1
spp=16
spp=64
spp=256
提高部分
這里實現(xiàn)了多線程加速部分的內(nèi)容,可以明顯加快渲染過程。具體實現(xiàn)過程不再贅述,這里僅貼一下代碼和不同線程下的渲染時長對比。
| 單線程 | 2m | 21m | 87m | ||
| 多線程 | 32s | 4m | 16m | 33m | 145m |
Render.cpp:
// // Created by goksu on 2/25/20. // #include <fstream>#include "Scene.hpp" #include "Renderer.hpp" #include <atomic> #include <thread>// 多線程加速渲染 inline float deg2rad(const float& deg) { return deg * M_PI / 180.0; }const float EPSILON = 0.00001; std::atomic_int progress = 0;// The main render function. This where we iterate over all pixels in the image, // generate primary rays and cast these rays into the scene. The content of the // framebuffer is saved to a file. void Renderer::Render(const Scene& scene) {std::vector<Vector3f> framebuffer(scene.width * scene.height);float scale = tan(deg2rad(scene.fov * 0.5));float imageAspectRatio = scene.width / (float)scene.height;Vector3f eye_pos(278, 273, -800);// change the spp value to change sample ammount// spp: sample per pixelint spp = 256; //原本16std::cout << "SPP: " << spp << "\n";int thred = 24;int per = scene.height/thred; // 960/24=40std::thread th[24]; //多線程auto renderRow = [&](uint32_t lrow, uint32_t hrow){for (uint32_t j = lrow; j < hrow; ++j) {for (uint32_t i = 0; i < scene.width; ++i) {// generate primary ray directionfloat x = (2 * (i + 0.5) / (float)scene.width - 1) *imageAspectRatio * scale;float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;Vector3f dir = normalize(Vector3f(-x, y, 1)); // ??? (x,y,-1) ???for (int k = 0; k < spp; k++){framebuffer[(int)(j*scene.width+i)] += scene.castRay(Ray(eye_pos, dir), 0) / spp; }}progress += 1;UpdateProgress(progress / (float)scene.height);}};for(int i=0;i<thred;i++){th[i] = std::thread(renderRow,i*per,(i+1)*per);}for(int i=0;i<thred;i++){th[i].join();}UpdateProgress(1.f);// save framebuffer to fileFILE* fp = fopen("binary.ppm", "wb");(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);for (auto i = 0; i < scene.height * scene.width; ++i) {static unsigned char color[3];color[0] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].x), 0.6f));color[1] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].y), 0.6f));color[2] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].z), 0.6f));fwrite(color, 1, 3, fp);}fclose(fp); }參考
Games101:作業(yè)7(含提高部分)_Q_pril的博客-CSDN博客_games101 作業(yè)7
GAMES101: 現(xiàn)代計算機圖形學入門
總結(jié)
以上是生活随笔為你收集整理的GAMES101蒙特卡洛光线追踪及Assignment7的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 碰撞检测算法分类
- 下一篇: pcap文件格式及其大文件切割——C语言