咱們上一篇寫了Chapter5 的第一個部分表面法線,那麼咱們來學剩下的部分,以及Chapter6.html
Chapter5:Surface normals and multiple objects.算法
咱們這一節主要向場景中添加對象。數組
依據代碼重用原則,此時應該抽象出對象創、繪製的公共部分dom
All what we do are followed by object-oriented ! ide
咱們先來抽象並定義一些基本的類型函數
1>.ray.測試
這個不用說了,可是咱們發現,在後面涉及到的全部的向量和精度類型均取決於ray,因此,咱們不妨把全部的抽象類放入統一的命名空間,把類型方面的定義放在空間內,而不是每次都須要ray::vec_typeui
/// ray.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the ray-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef RAY_H #define RAY_H #include <lvgm\type_vec\type_vec.h> //https://www.cnblogs.com/lv-anchoret/p/10163085.html namespace rt { using rtvar = lvgm::precision; using rtvec = lvgm::vec3<rtvar>; class ray { public: ray() :_a{ rtvec() } , _b{ rtvec() } { } ray(const rtvec& a, const rtvec& b) :_a(a) , _b(b) { } ray(const ray& r) :_a(r._a) , _b(r._b) { } inline rtvec origin()const { return _a; } inline rtvec direction()const { return _b; } inline rtvec go(const rtvar t)const { return _a + t * _b; } private: rtvec _a; rtvec _b; }; } #endif //ray_h
2>.intersect.spa
這個類名的由來是依據書中描述光線追蹤的一句話,我以爲總結的很精煉,我本身將它理解爲對光線追蹤的一個定義:指針
Ray Tracer is of the form calculate which ray goes from the eye to a pixel, compute what that ray intersects, and compute a color for that intersection ppoint.
而咱們這個類完成的就是前半部分:計算光線相交點,或者說是交叉點,或者說是撞擊點。
因此講基類命名爲intersect
由於在實際操做中可能須要對根進行條件過濾,因此,咱們在hit中增長了關於係數t的上限和下限,增長靈活度,強化用戶體驗。
/// intersect.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the intersect-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef INTERSECT_H #define INTERSECT_T #include "ray.h" namespace rt { struct hitInfo { lvgm::precision _t; //ray 中的係數t rtvec _p; //相交點、撞擊點 rtvec _n; //_p點的表面法線 }; class intersect { public: intersect() { } constexpr static rtvar inf() { return 0x3f3f3f3f; } //最大值 virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const = 0; virtual ~intersect() { } }; } #endif //INTERSECT_H
3>.sphere.
球體函數,撞擊函數和以前的hit同樣,只不過咱們優先選取比較小的根,由於它離咱們的視線更近,由於咱們看東西也是先看到的是近處的,遠處的被遮擋了。若是一個根都沒有,那麼咱們返回false
/// sphere.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the sphere-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef SPHERE_H #define SPHERE_H namespace rt { class sphere :public intersect { public: sphere() { } /* @para1: 球心座標 @para2: 球半徑 */ sphere(const rtvec& h, rtvar r) :_heart(h), _radius(r) { } /* @brief: 撞擊函數,求取撞擊點相關記錄信息 @param: sight->視線 係數t的上下界->篩選撞擊點 rec->返回撞擊點信息 @retur: 是否存在合法撞擊點 */ virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const override; /* @ get-functions */ inline const rtvar r()const { return _radius; } inline const rtvec& heart()const { return _heart; } inline rtvar& r() { return _radius; } inline rtvec& heart() { return _heart; } private: rtvec _heart; rtvar _radius; }; bool sphere::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const { rtvec trace = sight.origin() - _heart; 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) { rtvar x = (-b - sqrt(delt)) / (2.0*a); if (x < t_max && x > t_min) { rec._t = x; rec._p = sight.go(rec._t); rec._n = (rec._p - _heart) / _radius; return true; } x = (-b + sqrt(delt)) / (2.0*a); if (x < t_max && x > t_min) { rec._t = x; rec._p = sight.go(x); rec._n = (rec._p - _heart) / _radius; return true; } } return false; } } #endif
4>.intersections.
顧名思義,這個就是用於記錄多個交叉點的一個表
它包含一個二維指針,高維指的是一個有關於基類指針的數組,低維度就是指向基類——intersect的一個多態指針。
而它的hit函數就是,遍歷每個sphere對象,求取獲得視線穿過的離eye最近的交叉點。掃描屏幕的每一條視線均如此作,可翻閱上一篇,咱們的3條line的那個實線和虛線圖,對於每一條視線,若是與多個對象存在交叉點,那麼最短的那一條是實線,咱們求取的始終是實線部分,而實線的長,就是t
/// intersections.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the intersections-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef INTERSECTIONS_H #define INTERSECTIONS_H namespace rt { class intersections :public intersect { public: intersections() { } intersections(intersect** list, size_t n) :_list(list), _size(n) { } virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const override; private: intersect** _list; size_t _size; }; bool intersections::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const { hitInfo t_rec; bool hitSomething = false; rtvar far = t_max; //剛開始能夠看到無限遠 for (int i = 0; i < _size; ++i) { if (_list[i]->hit(sight, t_min, far, t_rec)) { hitSomething = true; far = t_rec._t; //將上一次的最近撞擊點做爲視線可達最遠處 rec = t_rec; } } return hitSomething; } } #endif //INTERSECTIONS_H
5>.camera
獲取視線
/// camera.h // ----------------------------------------------------- // [author] lv // [begin ] 2018.12 // [brief ] the camera-class for the ray-tracing project // from the 《ray tracing in one week》 // ----------------------------------------------------- #ifndef CAMERA_H #define CAMERA_H #include "ray.h" namespace rt { class camera { public: camera( const rtvec& eye = rtvec(0.,0.,0.), const rtvec& start = rtvec(-2., -1., -1.), const rtvec& horizon = rtvec(4., 0., 0.), const rtvec& vertical = rtvec(0., 2., 0.)) :_eye{ eye } ,_start{start} ,_horizontal{horizon} ,_vertical{vertical} { } inline const ray get_ray(const rtvar u,const rtvar v)const { return ray{ _eye, _start + u*_horizontal + v*_vertical }; } inline const ray get_ray(const lvgm::vec2<rtvar>& para)const { return ray{_eye, _start + para.u()*_horizontal + para.v()*_vertical}; } inline const rtvec& eye()const { return _eye; } inline const rtvec& start()const { return _start; } inline const rtvec& horizontal()const { return _horizontal; } inline const rtvec& vertical()const { return _vertical; } private: rtvec _eye; rtvec _start; rtvec _horizontal; rtvec _vertical; }; } #endif
------------ 完畢 --------------
進入正題,咱們今天來作多對象的場景
咱們還選用原來的球,那麼再添加一個看似草原的東東(我一開始認爲是草原)。
先上圖:
其實這個仍是比較簡單的,咱們在很遠處,想像那個座標系統,若是咱們在(0,-100.5,-1)處放一個半徑爲100的球,是不就是這樣了,而後,在屏幕空間內,小球的幾何表面比大球的幾何表面離眼睛更近,天然就會把小球凸顯出來
代碼:
#define LOWPRECISION #include <fstream> #include "intersect.h" #include "sphere.h" #include "intersections.h" #include "camera.h" #define stds std:: using namespace rt; rtvec lerp(const ray& sight, const intersect* world) { hitInfo rec; if (world->hit(sight, 0., intersect::inf(), rec)) return 0.5*rtvec(rec._n.x() + 1., rec._n.y() + 1., rec._n.z() + 1.); else { rtvec dirUnit = sight.direction().ret_unitization(); rtvar t = 0.5*(dirUnit.y() + 1.); return (1. - t)*rtvec(1., 1., 1.) + t*rtvec(0.5, 0.7, 1.0); } } void build_5_2() { stds ofstream file("graph5-2.ppm"); size_t W = 400, H = 200; if (file.is_open()) { file << "P3\n" << W << " " << H << "\n255\n" << stds endl; intersect** list = new intersect*[2]; list[0] = new sphere(rtvec(0, 0, -1), 0.5); list[1] = new sphere(rtvec(0, -100.5, -1), 100); intersect* world = new intersections(list, 2); camera cma; for (int y = H - 1; y >= 0; --y) for (int x = 0; x < W; ++x) { lvgm::vec2<rtvar> para{ rtvar(x) / W,rtvar(y) / H }; rtvec color = lerp(cma.get_ray(para), world); int r = int(255.99 * color.r()); int g = int(255.99 * color.g()); int b = int(255.99 * color.b()); file << r << " " << g << " " << b << stds endl; } stds cout << "complished" << stds endl; file.close(); if (list[0])delete list[0]; if (list[1])delete list[1]; if (list)delete[] list; } else stds cerr << "open file error" << stds endl; } int main() { build_5_2(); }
Chapter6:Antialiasing
這一章也是超簡單。
用最簡單的採樣模式對鋸齒進行修繕。
引用書中的圖片:
咱們掃描屏幕的每個點,獲得的水平步長和垂直步長u和v,可是咱們採用的都是整數點,而對於屏幕上的點來講應該是有無數個的對不對,而每一個點對應的顏色都是不同的,若是咱們把屏幕分辨率調的很是高,也就是把屏幕劃分地更加細微,鋸齒就會更小。
因此,咱們發現,在選取某個整數座標點進行着色的時候,咱們實際上是用整數座標的點的顏色覆蓋了周圍不少本應該是其餘顏色的點,就好比說上面的紅色方格,咱們以前選取的是方格中心的位置,進行計算獲得那一處的像素值,而後用它來代替整個方框的顏色
如今咱們賦予方格中心周圍的在方格內部的其餘點點的表達本身的權利。
就像投票
位於城市中心的周圍的小村莊也有發言權,他們各個小村莊之間的權利是平等的,咱們收集夠必定的票數,而後把值取平均做爲最後的像素值。
假設每一個整數點之間相隔一個單位,這樣咱們每一個方格的像素充分考慮了周圍[0,1)的像素值,在未觸及下一個整數座標點的全部範圍都考慮在內,那麼咱們相鄰兩個像素的顏色差就不會那麼突兀,就能夠顯得很是平滑了
以前鋸齒很明顯,是由於每一個像素格點只考慮了本身應有的顏色,未考慮兩個相鄰格點之間的漸變像素值,致使相鄰的兩個格點像素值差異較大,不平滑,因此出現鋸齒。
固然,增大分辨率是將相鄰兩個點的座標更加貼近,使得顏色差異不大。
我作一個Chapter5-1的球,而後再用採樣的方法,採起周圍50個隨機點的像素值取均值,進行對比
分辨率均爲200*100
原圖
採樣抗鋸齒圖:
能夠看出來平滑了不少
方法:採樣總值 = Σpixel_value(每一個座標份量+一個[0,1)隨機值造成的周圍採樣座標)
採樣結果 = 採樣總值/樣本數
std::uniform_real_distribution默認產生[0,1)的隨機值
std::mt19937是一種隨機生成算法,用此算法去初始化上面那個便可
測試以下:

事實證實,徹底能夠完成咱們的須要
代碼:
#define LOWPRECISION #include <fstream> #include "intersect.h" #include "sphere.h" #include "intersections.h" #include "camera.h" #include <random> #define stds std:: using namespace rt; stds mt19937 mt; stds uniform_real_distribution<rtvar> rtrand; rtvec lerp(const ray& sight, const intersect* world) { hitInfo rec; if (world->hit(sight, 0., intersect::inf(), rec)) return 0.5*rtvec(rec._n.x() + 1., rec._n.y() + 1., rec._n.z() + 1.); else { rtvec dirUnit = sight.direction().ret_unitization(); rtvar t = 0.5*(dirUnit.y() + 1.); return (1. - t)*rtvec(1., 1., 1.) + t*rtvec(0.5, 0.7, 1.0); } } void build_6_1() { stds ofstream file("graph6-2.ppm"); size_t W = 200, H = 100; if (file.is_open()) { file << "P3\n" << W << " " << H << "\n255\n" << stds endl; intersect** list = new intersect*[1]; list[0] = new sphere(rtvec(0, 0, -1), 0.5); //list[1] = new sphere(rtvec(0, -100.5, -1), 100); intersect* world = new intersections(list, 1); camera cma; for (int y = H - 1; y >= 0; --y) for (int x = 0; x < W; ++x) { rtvec color; for (int cnt = 0; cnt < 50; ++cnt) { lvgm::vec2<rtvar> para{ (rtrand(mt) + x) / W, (rtrand(mt) + y) / H }; color += lerp(cma.get_ray(para), world); } color /= 50; int r = int(255.99 * color.r()); int g = int(255.99 * color.g()); int b = int(255.99 * color.b()); file << r << " " << g << " " << b << stds endl; } stds cout << "complished" << stds endl; file.close(); if (list[0])delete list[0]; if (list)delete[] list; if (world)delete world; } else stds cerr << "open file error" << stds endl; } int main() { build_6_1(); }
感謝您的閱讀,生活愉快~