zoukankan      html  css  js  c++  java
  • 【Ray Tracing in One Weekend 超详解】 光线追踪1-10

    《Ray Tracing in One Weekend》完结篇

    最近课程上机实验,封面图渲染时间也超长,所以写东西就落下了,见谅

    这篇之后,我会继续《Ray Tracing The Next Week》,还请多多关注

    这几天我在渲染这本书的封面图,封面图还没出,不算结束,刚好安排了10节

    今天呢,有两件事:

    1.阐述整个工程的文件组织即内容

    2.阐述封面,完结

    12.1工程文件组织

    试过很多方法,问过很多老师,无奈,子类继承实现的父类纯虚函数实在无法和类声明分成两个文件(即声明放于.h,其他实现放在.cpp中),室友说,纯虚函数继承实现和模板很类似

    所以,我们在合适的时候使用hpp

    在学习过程中,我们遇到了诸如反射、折射之类的函数,它们并不应该属于某个具体子类,或者抽象基类

    所以,我把它们写在了泛型3D数学库里面了

    C++泛型3D数学库是我们学光线追踪的数学专用库了吧算是

    向量库

    基础光学几何函数库

     

    在回头看我们的光线追踪的项目代码

    1.工程定义文件

    我们之前是在ray这个最基本的类中定义了一些基本的命名,尔后,发现,所有的东西都要用ray::val_type诸如此类的代码去描述光线追踪所用到的一些普遍类型,这个非常麻烦,代码也长,后来,我们将它们移出了ray-class,放在了namespace rt中,但是,仍然放在ray文件中,这个很不合理,所以我们定义了一个RTdef.h,专门用于定义一些光线追踪的常量和命名

    /// RTdef.h
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2019.1.1
    // [brief ]        the basic concept of rt
    // -----------------------------------------------------
    #pragma once
    
    #include <lvgm	ype_vec	ype_vec.h>        //https://www.cnblogs.com/lv-anchoret/p/10163085.html
    #include <lvgmopticsfunc.hpp>            //https://www.cnblogs.com/lv-anchoret/p/10241904.html
    #include <lvgm
    andfunc.hpp>            //https://www.cnblogs.com/lv-anchoret/p/10241904.html
    
    namespace rt
    {
        using rtvar = lvgm::precision;
    
        using rtvec = lvgm::vec3<rtvar>;
    
        constexpr static rtvar rtInf() { return static_cast<rtvar>(0x3f3f3f3f); }        //最大值
    
        constexpr rtvar π = 3.1415926;
    
    }

    2.光线类

    /// ray.h
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        the ray-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    #pragma once
    
    #include "RTdef.h"
    
    namespace rt
    {
    
        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;
    
        };
    }

    3.相机类

    /// camera.h
    //https://www.cnblogs.com/lv-anchoret/p/10221058.html
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2019.1
    // [brief ]        the camera-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    
    #pragma once
    
    #include "ray.h"
    
    namespace rt
    {
    
    class camera
        {
    public:
        camera(rtvec lookfrom, rtvec lookat, rtvec vup, rtvar vfov, rtvar aspect, rtvar aperture, rtvar focus)
            :_eye(lookfrom)
            , _lens_radius(aperture / 2)
        {
            rtvar theta = vfov * π / 180;
            rtvar half_height = tan(theta / 2) * focus;        //tan(theta/2) = (height/2) / 焦距
            rtvar half_width = aspect * half_height;
            _w = (lookfrom - lookat).ret_unitization();
            _u = cross(vup, _w).ret_unitization();
            _v = cross(_w, _u);
    
            //向量运算
            _start = _eye - half_width * _u - half_height * _v - focus * _w;//高和宽都乘了焦距,w也要乘,不然公式是错的
            _horizontal = 2 * half_width * _u;
            _vertical = 2 * half_height * _v;
        }
    
        const ray get_ray(const rtvar u, const rtvar v)const
        {
            rtvec rd = rtvec(_lens_radius * lvgm::random_unit_plane());
            rtvec offset = _u * rd.x() + _v * rd.y();
            return ray{ _eye + offset, _start + u*_horizontal + v*_vertical - (_eye + offset) };
        }
    
        const ray get_ray(const lvgm::vec2<rtvar>& para)const    {    return get_ray(para.u(), para.v());    }
    
        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; }
    
        inline const rtvec& u()const { return _u; }
    
        inline const rtvec& v()const { return _v; }
    
        inline const rtvec& w()const { return _w; }
    
        inline const rtvar lens_r()const { return _lens_radius; }
    
    private:
        rtvec _u;
    
        rtvec _v;
    
        rtvec _w;
    
        rtvec _eye;
    
        rtvec _start;        //left-bottom
    
        rtvec _horizontal;
    
        rtvec _vertical;
    
        rtvar _lens_radius;  //the radius of lens
    
        };
    
    }

    4.碰撞相交部分

    有一个碰撞相交基类

    /// intersect.h
    //https://www.cnblogs.com/lv-anchoret/p/10190092.html
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        the intersect-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    #pragma once
    
    namespace rt
    {
        class material;
    
    struct hitInfo
        {
        lvgm::precision _t;        //ray 中的系数t
        rtvec _p;                //相交点、撞击点
        rtvec _n;                //_p点的表面法线
        material* materialp;    //材质
        };
    
    class intersect
        {
    public:
        intersect() {  }
    
        /*
        @brief: 撞击函数,求取撞击点相关记录信息
        @param: sight->视线
        系数t的上下界->筛选撞击点
        rec->返回撞击点信息
        @retur: 是否存在合法撞击点
        */
        virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& rec)const = 0;
    
        virtual ~intersect() {  }
        };
    
    }

    后面有一个子类intersections是用于处理一组碰撞相交的类,类比于容器

    /// intersections.h
    //    https://www.cnblogs.com/lv-anchoret/p/10190092.html
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        the intersections-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    #pragma once
    
    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;
    }
    
    
    }

    还有一个子类sphere是一种几何体用来做自身的碰撞检测的,之后,我们可能还会加入心形几何体类

    /// sphere.h
    //  https://www.cnblogs.com/lv-anchoret/p/10190092.html
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.1.1
    // [brief ]        the sphere-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    #pragma once
    
    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;
    }
    
    }

    一个总文件

    /// RThit.h
    //    https://www.cnblogs.com/lv-anchoret/p/10190092.html
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2019.1
    // [brief ]        some intersects
    //                intersections
    //                sphere
    //                heart
    // -----------------------------------------------------
    
    #pragma once
    
    #include "ray.h"
    #include "intersect.h"
    
    #include "sphere.hpp"
    #include "intersections.hpp"

    5.材质类

    材质有一个基类和三个子类

    /// material.h
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        the material-class for the ray-tracing project
    //                from the 《ray tracing in one week》
    // -----------------------------------------------------
    #pragma once
    
    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;
    
        };
    
    }
    /// diffuse.hpp
    // https://www.cnblogs.com/lv-anchoret/p/10198423.html
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        one of the materials
    // -----------------------------------------------------
    
    #pragma once
    
    namespace rt
    {
    //diffuse material
    class lambertian : public material
        {
    public:
        lambertian(const rtvec& a) :_albedo(a) {  }
    
        bool scatter(const ray& rIn, const hitInfo& info, rtvec& attenuation, ray& scattered)const override
        {
            rtvec target = info._p + info._n + lvgm::random_unit_sphere();
            scattered = ray{ info._p, target - info._p };
            attenuation = _albedo;
            return true;
        }
    protected:
    
        rtvec _albedo;
        };
    
    }
    /// metal.hpp
    // https://www.cnblogs.com/lv-anchoret/p/10206773.html
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2018.12
    // [brief ]        one of the materials
    // -----------------------------------------------------
    
    #pragma once
    
    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
        {
            rtvec target = reflect(rIn.direction().ret_unitization(), info._n);
            scattered = ray{ info._p, target + _fuzz * lvgm::random_unit_sphere() };
            attenuation = _albedo;
            return dot(scattered.direction(), info._n) != 0;
        }
    
        inline static rtvec reflect(const rtvec& in, const rtvec& n) { return in - 2 * dot(in, n)*n; }
        
    protected:
    
        rtvec _albedo;
    
        rtvar _fuzz;
        };
    
    }
    /// dielectric.hpp
    // https://www.cnblogs.com/lv-anchoret/p/10217719.html
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2019.1
    // [brief ]        one of the materials
    // -----------------------------------------------------
    #pragma once
    
    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;
    
        protected:
            rtvar _RI;
    
            inline rtvar schlick(const rtvar cosine)const;
        };
        
    
    
        bool dielectric::scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const
        {
            rtvec outward_normal;
            rtvec refracted;
            rtvec reflected = 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);    //如果有折射,计算反射系数
            else
                reflect_prob = 1.0;        //如果没有折射,那么为全反射
    
            if (lvgm::rand01() < reflect_prob)
                scattered = ray(info._p, reflected);
            else
                scattered = ray(info._p, refracted);
    
            return true;
        }
    
        inline rtvar dielectric::schlick(const rtvar cosine)const
        {
            rtvar r0 = (1. - _RI) / (1. + _RI);
            r0 *= r0;
            return r0 + (1 - r0)*pow((1 - cosine), 5);
        }
    }
    

    总文件

    /// RTmaterial.h
    
    // -----------------------------------------------------
    // [author]        lv
    // [begin ]        2019.1
    // [brief ]        some materials
    //                diffuse
    //                metal
    //                dielectric
    // -----------------------------------------------------
    #pragma once
    
    #include "ray.h"
    #include "intersect.h"
    #include "material.h"
    
    #include "diffuse.hpp"
    #include "metal.hpp"
    #include "dielectric.hpp"

    我们所有的文件就写完了

    12.2封面完结

    这个图让我学会了分段渲染。。。

    这个图非常好看,于是乎,整了一个600*400的,整整渲染了两天(....),内心是崩溃的

    我试过并发编程,结果效果不好(自己也不怎么会上锁。。。)

    所以,就只能单线程处理,时间超过十个小时左右吧,VS那个时间过程诊断框就坏死了。。。

    有时候,不能一直渲染,这个时候,被迫结束后,只需要读取已有文件的行数,然后计算出渲染了多少个点了,然后在渲染的双重for循环中从下一个点开始渲染写入文件即可,就可以随时随地想停就停,想渲染就渲染,因为图像本事就是一个一个像素点,我们只需要24w个点的文件数据即可,你也可以并发写入多个文件,最后拼在一起

    我们采用的相机参数是这样的

    据说是官方的,我也不清楚

    还有一个文章写得非常好,是写相机的参数测试的,大家可以阅读一下,对相机的参数有一个更直观深入的了解

    相机各个参数测试效果

    因为这个图实在是渲染了好久,所以也没有出一些其他的效果图,可能之后会更,大家可以自己设置球体以及相机,欢迎在评论区发出你的渲染图~

    下面是代码:

    #define LOWPRECISION
    
    #include <fstream>
    #include "RTmaterial.h"
    #include "RThit.h"
    #include "camera.h"
    #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);
        }
    }
    
    intersect* random_sphere()
    {
        int cnt = 500;
        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 = -11; a < 11; ++a)
            for (int b = -11; b < 11; ++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 sphere(center, 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);
    }
    
    void build_12_1()
    {
        stds ofstream file("graph12-1.ppm");
        size_t W = 200, H = 120, sample = 100;
    
        if (file.is_open())
        {
            file << "P3
    " << W << " " << H << "
    255
    " << stds endl;
    
            intersect* world = random_sphere();
    
            rtvec lookfrom(13, 2, 3);
            rtvec lookat(0, 0, 0);
            float dist_to_focus = (lookfrom - lookat).normal();
            float aperture = 0.0;
            camera cma(lookfrom, lookat, rtvec(0, 1, 0), 20, rtvar(W) / rtvar(H), aperture, 0.7*dist_to_focus);
    
            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{
                            (lvgm::rand01() + x) / W,
                            (lvgm::rand01() + 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 (world)delete world;
    
            stds cout << "complished" << stds endl;
        }
        else
            stds cerr << "open file error" << stds endl;
    }
    
    
    int main()
    {
        build_12_1();
    }

     感谢您的阅读,生活愉快~

  • 相关阅读:
    我的大学生涯
    如何设计一个好的Windows 8应用
    [置顶] 十大高明的Google搜索技巧
    [置顶] 走出困境靠自己
    Android代码混淆前后分析
    百度高级搜索
    未来手机什么样 十款未来概念手机图赏
    如何看懂Java混淆后的反编译代码
    最值得一看的几条简单的谷歌 Google 搜索技巧,瞬间提升你的网络搜索能力!
    各种网页尺寸判断方法
  • 原文地址:https://www.cnblogs.com/lv-anchoret/p/10243553.html
Copyright © 2011-2022 走看看