Preface
博主剛放假回家就進了醫院,今天剛完事兒,來續寫第二本書
Ready
我們來總結一下上一本書的筆記中我們的一些規定
1. 數學表達式
我們采用小寫粗黑體代表向量,大寫粗黑體代表矩陣,其他代表標量
2.我們將eye(or camera)發出的光線稱為視線(sight)
3.我們目前將所有的非特定(不專屬於任何一個類)的通用物理數學運算函數封裝到泛型庫中,並以lvgm命名空間限定,暫時不准備划分子命名空間
4.我們寫庫,擬采用全hpp書寫,因.h和.cpp之間關於父類純虛函數的子類相關聲明與實現出現了無法鏈接的神奇編譯錯誤,問過很多大佬,最后還是采用hpp
基礎工程代碼見第一本書總結篇
Chapter 1 Motion Blur
今天先來一個開胃菜,一個簡單易懂的技術——運動模糊
先看效果
好像我們之前也提到過類似的模糊,例如:散焦模糊(defucos blur)
它和今天的模糊還是有一定區別的,從物理成因上講,defucos是因為未正確對焦,如果沒有玩過生活中的相機也沒關系,大家都用手機拍過照吧,有些時候,手機相機打開對着拍攝對象,屏幕中的物體或字跡的邊緣是模糊的,通常我們會做一個動作,即,點一下屏幕(會出現一個小框),然后圖像就會變清晰(邊緣明顯)
運動模糊的意思是,現實世界中,相機快門開啟的時間間隔內,相機內物體發生了位移,畫面最后呈現出來的像素,是移動過程中像素的平均值。它不是由相機造成的,而是由物體運動狀態造成的形狀模糊,比如,你拍攝一張正在下落小球的照片,它所造成的模糊是在段時間內停留在歷史軌跡中的重影
弄清楚這個,就開始我們的正題
如何模擬真實生活中的運動模糊,我們先來回顧一下光線追蹤的過程
1. 我們選定屏幕中的一個位置,作為待計算的像素點
2. 從eye發出一條視線指向該上述位置周圍的采樣點
3. 如果中途有物體相交,那么根據物體表面材質進行視線計算
3. 至多遞歸計算50次,確定該位置的一個采樣值
4. 該位置周圍采樣100次,計算均值作為該位置的最終像素值
那么我們看一下上述過程中,需要改動哪些地方以支持模擬物體的運動模糊行為
首先,我們只需要模擬相機快門開啟的時間間隔內的物體運動情況,所以,物體一定有一個運動的起點和終點位置
如果是球體,我們用球心代表運動的位置(heart1,heart2),另外相機時間間隔是一段時間,需要起止時間點確定(time1,time2)
其次,我們模擬物體運動模糊,還要根據相機原理:畫面最后呈現出來的像素,是移動過程中像素的平均值
所以,我們依舊采用隨機取樣,我們之前已經確定采樣像素點為100個,采樣周圍點是為了抗鋸齒,這一節我們更重要的是要采樣物體的運動位置
我們有物體運動的起止位置和起止時間點(確定運動時間間隔),所以我們取0~1的隨機數作為物體從heart1到heart2運動路徑長度比例,進而確定物體在該隨機時刻的具體位置heart,我們取多個隨機時間進行采樣,之后再進行均值處理,作為移動過程中像素的平均值
完成上述理論,還需在光線追蹤過程中下手
第一步就不用說了,就是確定屏幕位置
第二步就很重要了,視線發出就要接觸物體,也就是說視線產生就要確定該時刻的物體具體位置,因為它要和物體進行相交計算,所以,視線承擔着記錄當前時間點的作用
而我們還需要做運動位置采樣,所以,eye需要發出多條視線,基於不同的隨機時間,這樣我們就做到了根據不同的隨機時間發出多條采樣視線對物體的運動位置進行采樣
我們不妨把抗鋸齒的采樣和運動位置采樣視線生成放在一起
ray.hpp
/// ray.h // ----------------------------------------------------- // [author] lv // [begin ] 2019.1 // [brief ] the ray-class for the ray-tracing project // from the 《ray tracing The Next week》 // ----------------------------------------------------- #pragma once #include "RTdef.h" namespace rt { class ray { public: ray() :_a{ rtvec() } , _b{ rtvec() } { } ray(const rtvec& a, const rtvec& b, const rtvar time = 0.) :_a(a) , _b(b) ,_time(time) { } ray(const ray& r) :_a(r._a) , _b(r._b) { } inline rtvec origin()const { return _a; } inline rtvec direction()const { return _b; } inline rtvar time()const { return _time; } inline rtvec go(const rtvar t)const { return _a + t * _b; } private: rtvec _a; rtvec _b; rtvar _time; }; }
camera.hpp
這樣,我們就做好了產生記錄隨機時間的視線
接下來就是采樣了
main.cpp
我們再來說第三步,視線和物體的相交計算
這個是最重要的,我們要 根據視線記錄的隨機時間,來確定物體的輪廓,然后進行計算交點
顯然,我們不能用之前的sphere了
下面是我們新寫的moving_sphere
/// moving_sphere.h // ----------------------------------------------------- // [author] lv // [begin ] 2019.1 // [brief ] the moving_sphere-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #pragma once #include "ray.h" #include "intersect.h" namespace rt { class moving_sphere :public intersect { public: moving_sphere() { } moving_sphere(rtvec heart1, rtvec heart2, rtvar t1, rtvar t2, rtvar r, material* mp) :_heart1(heart1) , _heart2(heart2) , _time1(t1) , _time2(t2) , _radius(r) , _materialp(mp) { } virtual bool hit(const ray& r, rtvar tmin, rtvar tmax, hitInfo& info)const override; inline rtvec heart(rtvar t)const { return _heart1 + ((t - _time1) / (_time2 - _time1)) * (_heart2 - _heart1); } private: rtvec _heart1; rtvec _heart2; rtvar _time1; rtvar _time2; rtvar _radius; material* _materialp; }; bool moving_sphere::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const { rtvec trace = sight.origin() - heart(sight.time()); rtvar a = dot(sight.direction(), sight.direction()); rtvar b = 2.0 * dot(trace, sight.direction()); rtvar c = dot(trace, trace) - _radius * _radius; rtvar delt = b*b - 4.0*a*c; if (delt > 0) { info.materialp = _materialp; rtvar x = (-b - sqrt(delt)) / (2.0*a); if (x < t_max && x > t_min) { info._t = x; info._p = sight.go(x); info._n = (info._p - heart(sight.time())) / _radius; return true; } x = (-b + sqrt(delt)) / (2.0*a); if (x < t_max && x > t_min) { info._t = x; info._p = sight.go(x); info._n = (info._p - heart(sight.time())) / _radius; return true; } } return false; } }
我們用vec3 heart(time);來計算某個隨機時間某個物體從開始位置向終止位置運動time時間之后的具體位置
第四步和第五步就不說了,都是上述過程的重復自動計算
下面是開篇圖片中球體參數設置
intersect* random_sphere() { int cnt = 50000; intersect **list = new intersect*[cnt + 1]; list[0] = new sphere(rtvec(0, -1000, 0), 1000, new lambertian(rtvec(0.5, 0.5, 0.5))); int size = 1; for (int a = -10; a < 10; ++a) for (int b = -10; b < 10; ++b) { rtvar choose_mat = lvgm::rand01(); rtvec center(a + 0.9 * lvgm::rand01(), 0.2, b + 0.9*lvgm::rand01()); if ((center - rtvec(4, 0.2, 0)).normal()>0.9) { if (choose_mat < 0.75) list[size++] = new moving_sphere(center, center + rtvec(0, 0.5*lvgm::rand01(), 0), 0., 1., 0.2, new lambertian(rtvec(lvgm::rand01()*lvgm::rand01(), lvgm::rand01()*lvgm::rand01(), lvgm::rand01()*lvgm::rand01()))); else if (choose_mat < 0.9) list[size++] = new sphere(center, 0.2, new metal(rtvec(0.5*(1 + lvgm::rand01()), 0.5*(1 + lvgm::rand01()), 0.5*(1 + lvgm::rand01())), 0.5*lvgm::rand01())); else list[size++] = new sphere(center, 0.2, new dielectric(1.5)); } } list[size++] = new sphere(rtvec(0, 1, 0), 1.0, new dielectric(1.5)); list[size++] = new sphere(rtvec(-4, 1, 0), 1.0, new lambertian(rtvec(0.4, 0.2, 0.1))); list[size++] = new sphere(rtvec(4, 1, 0), 1.0, new metal(rtvec(0.7, 0.6, 0.5), 0.)); return new intersections(list, size); }
相機參數設置
rtvec lookfrom(13, 2, 3); rtvec lookat(0, 0, 0); float dist_to_focus = 10.0; float aperture = 0.0; camera cma(lookfrom, lookat, rtvec(0, 1, 0), 20, rtvar(W) / rtvar(H), aperture, 0.7*dist_to_focus, 0., 1.);
還有水平運動模糊的圖片正在渲染中,完成之后會貼在下面
******************* 更新分割線 *************************
一個大球的水平運動模糊
此外你可以控制起止時間間隔或者起止位置遠近來調整小球運動模糊的速度
如果把上一張圖中運動大球的運動距離改為0.5長度,也就是
new moving_sphere(rtvec(-4.5, 1, 0.65), rtvec(-4.5,1,0.15)......
會得到下圖(球的數量和尺寸都改小了點)
代碼參見(https://www.cnblogs.com/lv-anchoret/p/10284085.html 末尾)
另外,之前的運動球過多,各種球體的比例也做了調整(第一張圖)
intersect* random_sphere() { int cnt = 50000; intersect **list = new intersect*[cnt + 1]; list[0] = new sphere(rtvec(0, -1000, 0), 1000, new lambertian(rtvec(0.5, 0.5, 0.5))); int size = 1; for (int a = -10; a < 10; ++a) for (int b = -10; b < 10; ++b) { rtvar choose_mat = lvgm::rand01(); rtvec center(a + 0.9 * lvgm::rand01(), 0.2, b + 0.9*lvgm::rand01()); if ((center - rtvec(4, 0.2, 0)).normal()>0.9) { if (choose_mat < 0.55) list[size++] = new moving_sphere(center, center + rtvec(0, 0.5*lvgm::rand01(), 0), 0., 1., 0.2, new lambertian(rtvec(lvgm::rand01()*lvgm::rand01(), lvgm::rand01()*lvgm::rand01(), lvgm::rand01()*lvgm::rand01()))); else if (choose_mat < 0.85) list[size++] = new sphere(center, 0.2, new metal(rtvec(0.5*(1 + lvgm::rand01()), 0.5*(1 + lvgm::rand01()), 0.5*(1 + lvgm::rand01())), 0.5*lvgm::rand01())); else list[size++] = new sphere(center, 0.2, new dielectric(1.5)); } } list[size++] = new sphere(rtvec(0, 1, 0), 1.0, new dielectric(1.5)); list[size++] = new moving_sphere(rtvec(-4.5, 1, 0.95), rtvec(-4,1,0.1), 0., 1., 1.0, new lambertian(rtvec(0.4, 0.2, 0.1))); list[size++] = new sphere(rtvec(4, 1, 0), 1.0, new metal(rtvec(0.7, 0.6, 0.5), 0.)); return new intersections(list, size); }
感謝您的閱讀,生活愉快~