【Ray Tracing in One Weekend 超详解】 光线追踪1-7 Dielectric 半径为负,实心球体镂空技巧...
?
今天講這本書(shū)最后一種材質(zhì)
?Preface
水,玻璃和鉆石等透明材料是電介質(zhì)。當(dāng)光線照射它們時(shí),它會(huì)分裂成反射光線和折射(透射)光線。
處理方案:在反射或折射之間隨機(jī)選擇并且每次交互僅產(chǎn)生一條散射光線
(實(shí)施方法:隨機(jī)取樣,具體見(jiàn)后文)
調(diào)試最困難的部分是折射光線。如果有折射光線的話,我通常首先讓所有的光折射。對(duì)于這個(gè)項(xiàng)目,我試圖在我們的場(chǎng)景中放置兩個(gè)玻璃球,我得到了這個(gè):
?
?
上述圖片是對(duì)的嗎?顯然,在實(shí)際生活中,那兩個(gè)玻璃球看起來(lái)怪怪的,實(shí)際情況下,里面的內(nèi)容應(yīng)該將現(xiàn)在的進(jìn)行上下顛倒,且沒(méi)有黑色成分。
?
Chapter9:Dielectrics?
?
?Ready
定量計(jì)算光的折射
?-------------------------------------------- 數(shù)學(xué)分割線?--------------------------------------------
公式中的η為相對(duì)折射率:n2/n1
而由于入射光線方向的隨機(jī)性和eta的不同,可能導(dǎo)致 1-η*η*(1-cosθ1 * cosθ1)小于0,此時(shí)取根號(hào)毫無(wú)意義
而事實(shí)上,這也就是全反射現(xiàn)象。即:當(dāng)光線從光密介質(zhì)進(jìn)入光疏介質(zhì)中如果入射角大于某個(gè)臨界值的時(shí)候,就會(huì)發(fā)生全反射現(xiàn)象。
該臨界角即折射角為90°時(shí)對(duì)應(yīng)的入射角,也就是cosθ2恰好等于0的時(shí)候
??------------------------------------------------ END?------------------------------------------------
?正文
我們來(lái)封裝一個(gè)電介質(zhì)類(lèi)
首先明確,它是材質(zhì)的一種,即
?
#ifndef DIELECTRIC_H #define DIELECTRIC_Hnamespace rt {class dielectric :public material{public:dielectric(rtvar RI) :_RI(RI) { }virtual bool scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const;inline bool refract(const rtvec& rIn, const rtvec& n, rtvar eta, rtvec& refracted)const;private:rtvar _RI; //refractive indices };bool dielectric::scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const{rtvec outward_normal;rtvec refracted;rtvar eta;attenuation = rtvec(1., 1., 1.);if (dot(rIn.direction(), info._n) > 0){outward_normal = -info._n;eta = _RI;}else{outward_normal = info._n;eta = 1. / _RI;}if (refract(rIn.direction(), outward_normal, eta, refracted)){scattered = ray(info._p, refracted);return true;}return false;}inline bool dielectric::refract(const rtvec& rIn, const rtvec& n, rtvar eta, rtvec& refracted)const{rtvec unitIn = rIn.ret_unitization();rtvar cos1 = dot(-unitIn, n);rtvar cos2 = 1. - eta*eta*(1 - cos1*cos2);if (cos2 > 0){refracted = eta * rIn + n*(eta*cos1 - sqrt(cos2));return true;}return false; //全反射 } }#endif dielectric.h?
?attenuation的值總是1,因?yàn)椴AП砻娌晃杖魏喂?#xff0c;即沒(méi)有rgb強(qiáng)度衰減
我們會(huì)很容易想到前言部分中的方法:如果有折射,那么讓所有的光線折射,就像上面代碼中scatter函數(shù)描述的那樣,那么就會(huì)得到那張圖
我們把metal中的reflect函數(shù)設(shè)置為靜態(tài)的,或者是命名空間內(nèi)“全局”函數(shù),這樣用起來(lái)比較方便,換句話講,這個(gè)公式并不屬于任何類(lèi),它是3D數(shù)學(xué)通用公式
main函數(shù)球體設(shè)置:
?
上述代碼是前言中圖像的生成代碼
然而,它沒(méi)有加入全反射,所以導(dǎo)致了黑色成分的出現(xiàn),所以,我們將全反射加入到上述代碼中
#ifndef DIELECTRIC_H #define DIELECTRIC_Hnamespace rt {class dielectric :public material{public:dielectric(const rtvar RI) :_RI(RI) { }virtual bool scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const override;inline bool refract(const rtvec& rIn, const rtvec& n, rtvar eta, rtvec& refracted)const{rtvec unitIn = rIn.ret_unitization(); //將入射光線單位化 rtvar cos1 = dot(unitIn, n);rtvar cos2 = 1. - eta*eta*(1. - cos1*cos1);if (cos2 > 0){refracted = eta * (rIn - n * cos1) - n * sqrt(cos2);return true;}return false;}private:rtvar _RI;};bool dielectric::scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const{rtvec outward_normal;rtvec reflected = metal::reflect(InRay.direction(), info._n);rtvar eta;attenuation = rtvec(1., 1., 1.);rtvec refracted;if (dot(InRay.direction(), info._n) > 0){outward_normal = -info._n;eta = _RI;}else{outward_normal = info._n;eta = 1. / _RI;}if (refract(InRay.direction(), outward_normal, eta, refracted)){scattered = ray(info._p, refracted);}else{scattered = ray(info._p, reflected);return false;}return true;}}#endif dielectric.h會(huì)得到如下圖:
?
?
?得到這張圖是真的不容易,踩了一天坑
?
主要是,渲染一張圖看下效果基本要7~10分鐘,玩不起,放開(kāi)雙手~~
?
?坑點(diǎn)
這里的反射公式有三種形式,但是它們化簡(jiǎn)之后都是一個(gè)式子
我們這里采用的是紙上推出來(lái)的,但是用哪個(gè)式子,我們都要注意三點(diǎn):
1.向量的符號(hào)!!!
我們知道cos(theta1) = dot(- 入射向量,法線)
折射向量 = eta * 入射 + 法線*eta*cos(theta1)- 法線 * cos(theta2)
但是,如果你代碼中的cos(theta1) = dot(入射,法線)
那么, ?折射向量 = eta * 入射 - .... ?這里就不是+了
這是公式的符號(hào)的問(wèn)題
2.入射向量的單位化
為什么要單位化呢,這個(gè)還是很重要的
因?yàn)槟銈魅氲娜肷湎蛄渴怯虚L(zhǎng)度的,你用你傳入的入射向量計(jì)算出來(lái)的折射向量也是有長(zhǎng)度的,顯然,折射不會(huì)衰減光的強(qiáng)度,也不會(huì)平白無(wú)故縮短向量
這時(shí)候你就要考慮了,你傳出的折射向量是要干嘛用的
折射向量是要作為新的視線的方向向量的對(duì)吧
而我們都知道,視線有三部分,eye的位置,方向向量,t系數(shù)(伸長(zhǎng)長(zhǎng)度)
還有一點(diǎn),我們計(jì)算景物的畫(huà)面的時(shí)候,計(jì)算的是視線延伸后的離眼球最近的點(diǎn)畫(huà)在屏幕上
如果,你的視線最初的方向向量本身就有好長(zhǎng),你的眼球好大一顆,那么本來(lái)離eye點(diǎn)最近的點(diǎn)可能就被這顆偌大的眼球邊界包在里面可能不是眼球之外最近的點(diǎn)了
所以,我們的方向向量一定是最短的,即單位1,這樣,我們伸長(zhǎng)之后,觸碰到的第一個(gè)點(diǎn)才能保證是離眼球最近的點(diǎn),如果方向向量過(guò)長(zhǎng),可能包在里面的點(diǎn)就被忽略了
第二點(diǎn)不注意就會(huì)出現(xiàn)下面這張圖
左球的景象少了些,可能就是上述原因,視線的方向向量太長(zhǎng)了,未經(jīng)過(guò)單位化
3.向量統(tǒng)一
如果你要用入射向量的單位向量,那么,所有涉及入射向量的地方都用入射單位向量代替
如果不用入射單位向量,那么整個(gè)代碼計(jì)算過(guò)程中就都不用,不要混用。
例: 下面是書(shū)上的折射函數(shù)代碼
函數(shù)體第一行,它把入射光單位化,第二行用 uv 做了點(diǎn)乘,然而后面的五行卻用的是 v 而不是uv ,沒(méi)道理!!第五行的 dt 和discriminant都是用 uv 算出來(lái)的,前面突然用個(gè)v是什么操作??
我們把v改成uv就可以了
?坑點(diǎn)結(jié)束
?
然而,還是存在玻璃內(nèi)圖像顛倒的現(xiàn)象
解釋如下:
?
?
這里面有一個(gè)反射系數(shù)的問(wèn)題,上面我們都考慮的是反射系數(shù)為0的情況,實(shí)際生活中的玻璃透明介質(zhì)是有反射系數(shù)的。
此時(shí),我們需要引入一個(gè)新的概念——反射系數(shù)
它是由 Christopher Schlick 提出的:
rtvar schlick(rtvar cosine, rtvar RI) {rtvar r0 = (1-RI)/(1+RI);r0 *= r0;return r0 + (1-r0)*pow((1-cosine),5); }?這里面還有一個(gè)問(wèn)題
?我們折射的 scatter 函數(shù)需要全反射的時(shí)候return 的 是false , 意思是 if 只計(jì)算折射情況,全反射是按照 rtvec(0,0,0)運(yùn)算的,壓根就沒(méi)算
?
所以,我們改一下代碼:
#ifndef DIELECTRIC_H #define DIELECTRIC_Hnamespace rt {class dielectric :public material{public:dielectric(const rtvar RI) :_RI(RI) { }virtual bool scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const override;inline static bool refract(const rtvec& rIn, const rtvec& n, rtvar eta, rtvec& refracted);protected:rtvar _RI;rtvar dielectric::schlick(const rtvar cosine, const rtvar RI)const;};bool dielectric::scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const{rtvec outward_normal;rtvec refracted;rtvec reflected = metal::reflect(InRay.direction(), info._n);rtvar eta;rtvar reflect_prob;rtvar cos;attenuation = rtvec(1., 1., 1.);if (dot(InRay.direction(), info._n) > 0){outward_normal = -info._n;eta = _RI;cos = _RI * dot(InRay.direction(), info._n) / InRay.direction().normal();}else{outward_normal = info._n;eta = 1.0 / _RI;cos = -dot(InRay.direction(), info._n) / InRay.direction().normal();}if (refract(InRay.direction(), outward_normal, eta, refracted))reflect_prob = schlick(cos, _RI); //如果有折射,計(jì)算反射系數(shù)elsereflect_prob = 1.0; //如果沒(méi)有折射,那么為全反射if (rtrand01() < reflect_prob)scattered = ray(info._p, reflected);elsescattered = ray(info._p, refracted);return true;}inline bool dielectric::refract(const rtvec& rIn, const rtvec& n, rtvar eta, rtvec& refracted){rtvec unitIn = rIn.ret_unitization(); //將入射光線單位化 rtvar cos1 = dot(-unitIn, n);rtvar cos2 = 1. - eta*eta*(1. - cos1*cos1);if (cos2 > 0){refracted = eta * unitIn + n * (eta * cos1 - sqrt(cos2));return true;}return false;}rtvar dielectric::schlick(const rtvar cosine, const rtvar RI)const{rtvar r0 = (1. - RI) / (1. + RI);r0 *= r0;return r0 + (1 - r0)*pow((1 - cosine), 5);} }#endif dielectric.h?
里面涉及到了rtrand01,還記得嗎,這個(gè)是我們?cè)趯W(xué)漫反射的時(shí)候弄的
那么放在這里作什么嘞?
還記得Preface中我們說(shuō)過(guò)的處理方案嗎
?
我們現(xiàn)在就是這么做的,我們得到一個(gè)reflect_prob,它介于0~1之間,如果我們?nèi)?~1之間的隨機(jī)數(shù),根據(jù)隨機(jī)數(shù)確定選擇反射還是折射,這個(gè)還是很科學(xué)的,為什么呢?因?yàn)槲覀冏隽?00次采樣!!,那么我們可以理直氣壯的說(shuō),我們的透明電介質(zhì)真正做到了反射和折射的混合(除了全反射現(xiàn)象),而且,前言也說(shuō)過(guò),光線照射透明電介質(zhì)時(shí),它會(huì)分裂為反射光線和折射光線。
?
主函數(shù):
#define LOWPRECISION#include ...... #define stds std:: using namespace rt;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);elsereturn 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);} }void build_9_1() {stds ofstream file("graph9-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.1, 0.2, 0.5)));list[1] = new sphere(rtvec(0, -100.5, -1), 100, new lambertian(rtvec(0.8, 0.8, 0.)));list[2] = new sphere(rtvec(-1, 0, -1), 0.5, new dielectric(1.5));list[3] = 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;}elsestds cerr << "open file error" << stds endl; }int main() {build_9_1(); }/*********************************************************/?
電介質(zhì)球體的一個(gè)有趣且簡(jiǎn)單的技巧是要注意,如果使用負(fù)半徑,幾何體不受影響但表面法線指向內(nèi)部,因此它可以用作氣泡來(lái)制作空心玻璃球體:
?
我們實(shí)驗(yàn)一下書(shū)上的負(fù)半徑:
?
得到這樣的圖:
?
?
?
?
?為了能夠看懂空心球是個(gè)啥玩意兒,我把eta 顛倒了一下
?
dot小于0,說(shuō)明入射光線是從表面法線指向的方向空間入射到內(nèi)部空間,例如:光從空氣入射到水中
dot大于0,說(shuō)明入射光線是從表面法線的反方向空間入射到表面法線指向的空間
這樣,我們就可以看到那個(gè)內(nèi)球了
?
?原著
本章節(jié)原書(shū)pdf圖片,可放大
點(diǎn)此處查看或者翻閱相冊(cè)內(nèi)容
?
感謝您的閱讀,生活愉快~
轉(zhuǎn)載于:https://www.cnblogs.com/lv-anchoret/p/10217719.html
總結(jié)
以上是生活随笔為你收集整理的【Ray Tracing in One Weekend 超详解】 光线追踪1-7 Dielectric 半径为负,实心球体镂空技巧...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python小猪蹄儿
- 下一篇: C#程序中设置全局代理(Global P