玖富娱乐平台全网唯一指定1956注册开户网站

【Ray Tracing in One Weekend 超详解】 光芒追踪1-7 Di

日期:2019-01-04 浏览:
玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。

 

本日讲这本书末了一种材质

 Preface

水,玻璃和钻石等通明资料是电介质。当光芒照耀它们时,它会分裂成反射光芒和折射(透射)光芒。

处置惩罚计划:在反射或折射之间随机挑选并且每次交互仅发作一条散射光芒

(实行要领:随机取样,详细见后文)

调试最难题的局部是折射光芒。若是有折射光芒的话,我一般起首让一切的光折射。关于这个项目,我试图在我们的场景中安排两个玻璃球,我获得了这个:

   

 

上述图片是对的吗?明显,在现实生涯中,那两个玻璃球看起来怪怪的,现实状况下,内里的内容应当将如今的举行上下倒置,且没有黑色身分。

 

Chapter9:Dielectrics 

 

 Ready

定量盘算光的折射

 -------------------------------------------- 数学分割线 --------------------------------------------

公式中的η为相对折射率:n2/n1

而由于入射光芒偏向的随机性和eta的分歧,可以或许致使 1-η*η*(1-cosθ1 * cosθ1)小于0,此时取根号毫无意义

而事实上,这也就是全反射征象。即:当光芒从光密介质进入光疏介质中若是入射角大于某个临界值的时刻,就会发作全反射征象。

该临界角即折射角为90°时对应的入射角,也就是cosθ2正好即是0的时刻

  ------------------------------------------------ END ------------------------------------------------

 正文

我们来封装一个电介质类

起首明白,它是材质的一种,即

 

#ifndef DIELECTRIC_H
#define DIELECTRIC_H

namespace 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,由于玻璃外面不吸收任何光,即没有rgb强度衰减

我们会很轻易想到媒介局部中的要领:若是有折射,那末让一切的光芒折射,就像上面代码中scatter函数形貌的那样,那末就会获得那张图

我们把metal中的reflect函数设置为静态的,或许是定名空间内“全局”函数,如许用起来对照轻易,换句话讲,这个公式其实不属于任何类,它是3D数学通用公式

main函数球体设置:

 

上述代码是媒介中图象的天生代码

然则,它没有到场全反射,以是致使了黑色身分的涌现,以是,我们将全反射到场到上述代码中

#ifndef DIELECTRIC_H
#define DIELECTRIC_H

namespace 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

会获得以下图:

 

 

 获得这张图是真的不轻易,踩了一天坑

 

主如果,衬着一张图看下结果基础要7~10分钟,玩不起,摊开双手~~

 

 坑点

这里的反射公式有三种情势,然则它们化简以后都是一个式子

我们这里接纳的是纸上推出来的,然则用哪一个式子,我们都要注重三点:

1.向量的标记!!!

我们晓得cos(theta1) = dot(- 入射向量,法线)

    折射向量 = eta * 入射 法线*eta*cos(theta1)- 法线 * cos(theta2)

然则,若是你代码中的cos(theta1) = dot(入射,法线)

那末,  折射向量 = eta * 入射 - ....  这里就不是 了

这是公式的标记的题目

2.入射向量的单元化

为何要单元化呢,这个照样很主要的

由于你传入的入射向量是有长度的,你用你传入的入射向量盘算出来的折射向量也是有长度的,明显,折射不会衰减光的强度,也不会无缘无故收缩向量

这时刻你就要斟酌了,你传出的折射向量是要干吗用的

折射向量是要作为新的视线的偏向向量的对吧

而我们都晓得,视线有三局部,eye的地位,偏向向量,t系数(伸长长度)

另有一点,我们盘算风景的画面的时刻,盘算的是视线延长后的离眼球近来的点画在屏幕上

若是,你的视线最后的偏向向量自身就有好长,你的眼球好大一颗,那末正本离eye点近来的点可以或许就被这颗偌大的眼球界限包在内里可以或许不是眼球以外近来的点了

以是,我们的偏向向量一定是最短的,即单元1,如许,我们伸长以后,触遇到的第一个点能力包管是离眼球近来的点,若是偏向向量太长,可以或许包在内里的点就被忽略了

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。-

第二点不注重就会涌现下面这张图

  

左球的现象少了些,可以或许就是上述缘由,视线的偏向向量太长了,未经由单元化

3.向量一致

若是你要用入射向量的单元向量,那末,一切触及入射向量的处所都用入射单元向量替代

若是不消入射单元向量,那末全部代码盘算过程当中就都不消,不要混用。

例: 下面是书上的折射函数代码

函数体第一行,它把入射光单元化,第二行用 uv 做了点乘,然则背面的五行却用的是 v 而不是uv ,没道理!!第五行的 dt 和discriminant都是用 uv 算出来的,前面倏忽用个v是甚么操纵??

我们把v改成uv就可以或许了

 坑点完毕

 

然则,照样存在玻璃内图象倒置的征象

诠释以下:

 

 

这内里有一个反射系数的题目,上面我们都斟酌的是反射系数为0的状况,现实生涯中的玻璃通明介质是有反射系数的。

此时,我们须要引入一个新的观点——反射系数

它是由 Christopher Schlick 提出的:

rtvar schlick(rtvar cosine, rtvar RI)
{
    rtvar r0 = (1-RI)/(1 RI);
    r0 *= r0;
    return r0   (1-r0)*pow((1-cosine),5);
}

 这内里另有一个题目

 我们折射的 scatter 函数须要全反射的时刻return 的 是false , 意义是 if 只盘算折射状况,全反射是依照 rtvec(0,0,0)运算的,压根就没算

 

以是,我们改一下代码:

 

#ifndef DIELECTRIC_H
#define DIELECTRIC_H

namespace 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);    //若是有折射,盘算反射系数
        else
            reflect_prob = 1.0;        //若是没有折射,那末为全反射

        if (rtrand01() < reflect_prob)
            scattered = ray(info._p, reflected);
        else
            scattered = 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 * rIn   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,还记得吗,这个是我们在学漫反射的时刻弄的

那末放在这里作甚么嘞?

还记得Preface中我们说过的处置惩罚计划吗

 

我们如今就是这么做的,我们获得一个reflect_prob,它介于0~1之间,若是我们取0~1之间的随机数,依据随机数肯定挑选反射照样折射,这个照样很科学的,为何呢?由于我们做了100次采样!!,那末我们可以或许义正词严的说,我们的通明电介质真正做到了反射和折射的夹杂(除全反射征象),并且,媒介也说过,光芒照耀通明电介质时,它会分裂为反射光芒和折射光芒。

 

主函数:

#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);
        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);
    }
}

void build_9_1()
{
    stds ofstream file("graph9-1.ppm");
    size_t W = 400, H = 200, sample = 100;

    if (file.is_open())
    {
        file << "P3n" << W << " " << H << "n255n" << 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;
    }
    else
        stds cerr << "open file error" << stds endl;
}

int main()
{
    build_9_1();
}

/*********************************************************/

 

电介质球体的一个风趣且简朴的技能是要注重,若是运用负半径,几何体不受影响但外面法线指向内部,因而它可以或许用作气泡来制造空心玻璃球体:

 

我们试验一下书上的负半径:

 

获得如许的图:

 

 为了可以或许看懂空心球是个啥玩艺儿,我把eta 倒置了一下

 

dot小于0,申明入射光芒是从外面法线指向的偏向空间入射到内部空间,比方:光从氛围入射到水中

dot大于0,申明入射光芒是从外面法线的反偏向空间入射到外面法线指向的空间

如许,我们就可以或许看到谁人内球了

 

 

 原著

本章节原书pdf图片,可放大

点此处检察或许翻阅相册内容

 

感谢您的浏览,生涯兴奋~

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。


平台知识

联系方式丨CONTACT

  • 全国热线:7711177
  • 传真热线:010-88888888
  • Q Q咨询:7711177
  • 企业邮箱:
首页
电话
短信