【Ray Tracing in One Weekend 超詳解】 光線追蹤1-6

 

新的一年,前來打卡html

 Prefacedom

回顧上一篇,咱們講述了漫反射材質,也就是平時的磨砂表面。ide

它一種將入射光經表面隨機散射造成的材質,是一種很是廣泛的表面形式。函數

這一篇,咱們未來學習鏡面反射,或者說是金屬材質學習

鏡面在生活中見得也不少,它是一種將入射光經表面按照物理反射規律造成的材質。測試

 

 先看效果 spa

 

 Ready3d

以前咱們就寫好的code

ray.horm

intersect.h

intersection.h

sphere.h

camera.h

 

  Chapter8: Metal

以前咱們已經寫過一個漫反射的材質,能夠發現,材質其實就解決兩個問題:

1.如何創造反射光或者散射光(吸取轉化入射光)

2.如何肯定光線強度的衰減量

咱們採用類比法:

上一篇中

diffuse表面:1.視線與物體表面產生撞擊點p,在p處相切單位圓內隨機找一點s,散射光方向即p->s

       2.咱們上一篇採用的光線強度衰減機制是取半。

這一篇中咱們將

metal表面: 1.根據物理反射定律肯定入射光對應的反射光的方向

      2.強度衰減改成三元組,分別對應rgb三份量的衰減度,且用參數自由肯定

 

那麼首先,它們有共同點,咱們有必要將其抽象一下

/// material.h

// ----------------------------------------------------- // [author] lv // [begin ] 2018.1.1 // [brief ] the material-class for the ray-tracing project // from the 《ray tracing in one week》 // -----------------------------------------------------
 #ifndef MATERIAL_H #define MATERIAL_H

namespace rt { //abstract basic class
class material { public: /* @brief: produce a scattered ray @param: InRay -> Incident light info -> the information of intersect-point(hit-point) attenuation -> when scattered, how much the ray should be attenuated by tis reflectance R scattered -> as we talk, it is a new sight; or it is the scattered ray with the intersect-point @retur: the function calculate a scattered ray or not */
    virtual bool scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const = 0; protected: /* @brief: find a random point in unit_sphere */
    const rtvec random_unit_sphere()const { rtvec p; do { p = 2.0*rtvec(rtrand01(), rtrand01(), rtrand01()) - rtvec(1, 1, 1); } while (dot(p, p) >= 1.0); return p; } }; } #endif

 

書上是這樣的:

 

可是取單位圓隨機點在兩個材質中都有用到,因此,我仍是選擇把它放在了基類中,可能做者在後面會進行添加,這個不作討論。

咱們繼續看一下,若是咱們定義了材質,那麼咱們須要改一些其餘的文件內容,將它融入進去

intersect.h中的hitInfo中須要添加

 

咱們如今定義漫反射材質(Diffuse or Lambertian)以下:

/// diffuse.h

// ----------------------------------------------------- // [author] lv // [begin ] 2019.1.1 // [brief ] one of the materials // -----------------------------------------------------
 #ifndef DIFFUSE_H #define DIFFUSE_H

namespace rt { //diffuse material
class lambertian : public material { public: lambertian(const rtvec& a) :_albedo(a) { } virtual bool scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const override; protected: rtvec _albedo; }; bool lambertian::scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const { rtvec target = info._p + info._n + random_unit_sphere(); scattered = ray{ info._p, target - info._p }; attenuation = _albedo; return true; } } #endif
diffuse.h

scatter函數就是上次主函數裏面寫的 lerp()

_albedo爲衰減三元組,下同,再也不贅述

 

接下來,咱們須要瞭解一下,反射定律;

 

 因此,咱們的反射函數以下:

inline rtvec reflect(const rtvec& in, const rtvec& n)const { return in - 2 * dot(in, n)*n; }

 

 而後咱們就能夠寫金屬材質了

/// metal.h

// ----------------------------------------------------- // [author] lv // [begin ] 2018.1.1 // [brief ] one of the materials // -----------------------------------------------------
 #ifndef MEATL_H #define METAL_H

namespace rt { //metal material
class metal :public material { public: metal(const rtvec& a) :_albedo(a) { } virtual bool scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const override; protected: inline rtvec reflect(const rtvec& in, const rtvec& n)const { return in - 2 * dot(in, n)*n; } rtvec _albedo; }; bool metal::scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const { rtvec target = reflect(rIn.direction().ret_unitization(), info._n); scattered = ray{ info._p, target }; attenuation = _albedo; return dot(scattered.direction(), info._n) != 0; } } #endif
metal.h

 

這個其實比較簡單,就根據反射定律計算出反射向量而後轉移視線便可

根據書上的步驟,咱們能夠先寫一個例子了

咱們首先寫lerp函數

爲了不場景中物體過多,進行很是屢次反射下降渲染效率,咱們取合適的反射遞歸深度值做爲界限

rtvec lerp(const ray& sight, intersect* world, int depth) { hitInfo info; if (world->hit(sight, (rtvar)0.001, rtInf(), info)) { ray scattered; rtvec attenuation; if (depth < 50 && info.materialp->scatter(sight, info, attenuation, scattered)) return attenuation * lerp(scattered, world, depth + 1); //遞歸反射,每次反射回退計算rgb的時候進行衰減 else
            return rtvec(0, 0, 0); } else { rtvec unit_dir = sight.direction().ret_unitization(); rtvar t = 0.5*(unit_dir.y() + 1.); return (1. - t)*rtvec(1., 1., 1.) + t*rtvec(0.5, 0.7, 1.0); } }

 

 咱們的main函數:

 

inline rtvar rtrand01() //http://www.javashuo.com/article/p-mutrzogz-eb.html { static std::mt19937 mt; static std::uniform_real_distribution<rtvar> rtrand; return rtrand(mt); }

 

main:

stds ofstream file("graph8-1.ppm"); size_t W = 400, H = 200, sample = 100; if (file.is_open()) { file << "P3\n" << W << " " << H << "\n255\n" << stds endl; size_t sphereCnt = 4; intersect** list = new intersect*[sphereCnt]; list[0] = new sphere(rtvec(0, 0, -1), 0.5, new lambertian(rtvec(0.8,0.3,0.3))); list[1] = new sphere(rtvec(0, -100.5, -1), 100, new lambertian(rtvec(0.8, 0.8, 0.))); list[3] = new sphere(rtvec(-1, 0, -1), 0.5, new metal(rtvec(0.8, 0.8, 0.8))); list[2] = new sphere(rtvec(1, 0, -1), 0.5, new metal(rtvec(0.8, 0.6, 0.2))); intersect* world = new intersections(list, sphereCnt); camera cma; for (int y = H - 1; y >= 0; --y) for (int x = 0; x < W; ++x) { rtvec color; for (int cnt = 0; cnt < sample; ++cnt) { lvgm::vec2<rtvar> para{ (rtrand01() + x) / W, (rtrand01() + y) / H }; color += lerp(cma.get_ray(para), world, 0); } color /= sample; color = rtvec(sqrt(color.r()), sqrt(color.g()), sqrt(color.b()));    //gamma 校訂,上一篇講過
                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; } file.close(); if (list[0])delete list[0]; if (list[1])delete list[1]; if (list[2])delete list[2]; if (list[3])delete list[3]; if (list)delete[] list; if (world)delete world; stds cout << "complished" << stds endl; } else stds cerr << "open file error" << stds endl;

 上述的sphere對象增長了材質,因此咱們須要爲sphere-class作一些適當的補充

/// sphere.h

// ----------------------------------------------------- // [author] lv // [begin ] 2018.1.1 // [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: 球半徑 @para3: 材質 */ sphere(const rtvec& h, rtvar r, material* ma) :_heart(h), _radius(r), _materialp(ma) { } ~sphere() { if (_materialp)    delete _materialp; } virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const override; 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; material* _materialp; }; 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) { rec.materialp = _materialp; 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
sphere.h

 

咱們建立了四個球

中間heart:(0,0,1)  r:0.5

下面heart:(0,-100.5,-1)  r:100

左邊heart:(-1,0,-1)  r:0.5

右邊heart:(1,0,-1)  r:0.5

左右爲鏡面,中間和下面是磨砂

 

 回顧咱們的標準屏幕座標系:coor 1.1

 中間球的球心 ,距上邊界爲1,距下邊界爲1,距左邊界爲2,距右邊界爲2

因此,綠色球(heart(0,-100.5,-1), r:100)超出屏幕底部0.5,意思是和三個球的底部是契合的,因此,它們之間有三個接觸的陰影

而左右兩個球中的畫面均爲鏡面反射,並非透明,中間球兩邊的小球是在旁邊球面的球面鏡像

咱們能夠測驗下,好比把綠球的半徑改成100.3,即

則是這樣的:

 

 如今總該相信,綠球的上邊界並非圖中的綠色橫線,那些都是左右球鏡面反射的鏡像。

你也能夠把綠球的半徑改成99.7

 

 三個球的底部和綠球並無接觸陰影,且球鏡面鏡像中綠色橫線邊界有所下降

 

若是沒有明白,咱們來屢一下流程再繼續往下走:

 流程

1.咱們先建立幾個sphere,每一個都須要有球心、半徑、rgb衰減三元組和材質

2.視線掃描屏幕

3.lerp計算

  1)當前視線和場景中全部的物體求表面交點,求最近點,順便把交點的信息都記錄下來,包括位置,表面法線和該點所在的sphere中的材質信息

  2)若是有交點:根據交點的材質,計算反射或散射向量,順便把材質中的衰減三元組信息經過參數傳出來,而後返回rgb的時候進行rgb份量衰減,根據求取的scattered-ray,進行視線轉移(視點轉換);若是沒有交點了,那麼返回該位置對應的背景插值顏色

4.採樣

5.gamma校訂

6.輸出屏幕中該點的信息

 

那麼,咱們仍是來關注下這裏面的一些個有趣的事情,好像有一個叫衰減三元組的,使用計算反射後的光線的rgb乘以三元組進行份量衰減,那麼,若是衰減三元組爲(1,1,1),那麼意思就是保持原值,未損失,那麼咱們把場景中全部的sphere中的衰減三元組均改成(1,1,1),會是什麼樣子的呢?

很是不明顯,尤爲是中間和下面,基本看不到了,右邊還算有些輪廓

由於,漫反射材質散射方向隨機,因此若是不把散射光進行逐步衰減的話,基本就是周圍背景色,因此,漫反射材質很容易融入壞境

而鏡面是嚴格的物理反射規律,因此上半部分會用更上面的光代替,下面的會用下面的光代替,因此仍是有一些色差的

左面的部分還加了鏡面模糊效果的,鏡面模糊下面講

 

鏡面模糊其實就是 鏡面 + 模糊係數*漫反射

漫反射實現原理是根據隨機化s點,因此模糊鏡面實現公式即爲:

模糊鏡面反射 = 鏡面反射 + 模糊係數 * 單位球隨機點漫反射

引用書中一張圖:

模糊原理就和漫反射原理差很少

 

/// metal.h

// ----------------------------------------------------- // [author] lv // [begin ] 2018.1.1 // [brief ] one of the materials // -----------------------------------------------------
 #ifndef MEATL_H #define METAL_H

namespace rt { //metal material
class metal :public material { public: metal(const rtvec& a, const rtvar f = 0.) :_albedo(a) { if (f < 1 && f >= 0)_fuzz = f; else _fuzz = 1; } virtual bool scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const override; protected: inline rtvec reflect(const rtvec& in, const rtvec& n)const { return in - 2 * dot(in, n)*n; } rtvec _albedo; rtvar _fuzz; }; bool metal::scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const { rtvec target = reflect(rIn.direction().ret_unitization(), info._n); scattered = ray{ info._p, target + _fuzz * random_unit_sphere() }; attenuation = _albedo; return dot(scattered.direction(), info._n) != 0; } } #endif

 

 因此咱們在main中建立sphere時,還要指定模糊係數,默認爲0(不模糊)

 

咱們來測試下模糊係數,若是左右兩個鏡面的模糊係數分別爲0.7和0.2的話,是這個樣子的:

 

 若是隻把右邊和下邊改成鏡面,那麼就頗有意思了:

 

最後一張,全鏡面,左球和中球模糊

 

 是否是感受很是有意思

 

 遺留工程問題

一個基類material,裏面一個純虛函數scatter

兩個子類,metal和Lambertian

兩個子類的類聲明放在頭文件中,將scatter函數實現放在源文件中

會有一個子類的scatter沒法解析

 

感謝您的閱讀,生活愉快~

相關文章
相關標籤/搜索