【Ray Tracing in One Weekend 超详解】 光线追踪1-4

 

咱们上一篇写了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
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
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
sphere.h

 

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
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
camera.h

 

------------ 完毕 --------------

 

进入正题,咱们今天来作多对象的场景

咱们还选用原来的球,那么再添加一个看似草原的东东(我一开始认为是草原)。

先上图:

其实这个仍是比较简单的,咱们在很远处,想像那个坐标系统,若是咱们在(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

 这一章也是超简单。

用最简单的采样模式对锯齿进行修缮。

引用书中的图片:

咱们扫描屏幕的每个点,获得的水平步长和垂直步长uv,可是咱们采用的都是整数点,而对于屏幕上的点来讲应该是有无数个的对不对,而每一个点对应的颜色都是不同的,若是咱们把屏幕分辨率调的很是高,也就是把屏幕划分地更加细微,锯齿就会更小。

因此,咱们发现,在选取某个整数坐标点进行着色的时候,咱们实际上是用整数坐标的点的颜色覆盖了周围不少本应该是其余颜色的点,就好比说上面的红色方格,咱们以前选取的是方格中心的位置,进行计算获得那一处的像素值,而后用它来代替整个方框的颜色

如今咱们赋予方格中心周围的在方格内部的其余点点的表达本身的权利。

就像投票

位于城市中心的周围的小村庄也有发言权,他们各个小村庄之间的权利是平等的,咱们收集够必定的票数,而后把值取平均做为最后的像素值。

假设每一个整数点之间相隔一个单位,这样咱们每一个方格的像素充分考虑了周围[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();
}

 

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

相关文章
相关标签/搜索