【Ray Tracing The Next Week 超详解】 光线追踪2-7 任意长方体 && 场景案例

上一篇比较简单,很久才发是因为做了一些好玩的场景,后来发现这一章是专门写场景例子的,所以就安排到了这一篇

Preface

这一篇要介绍的内容有:

1. 自己做的光照例子

2. Cornell box画质问题及优化方案

3. 新的场景几何体——长方体

  轴平行长方体

  任意长方体

我们这一篇重实践轻理论阐述

ready

1. 需要上一章的知识

但是,上一章的Cornell box画质优化仅限于盒子本身,如果作为场景和其他物体放在一起就不能那么优化画质

即,Cornell box像素计算失败应该返回黑色点而非白色

2. 需要图形学基本仿射变换知识

3. 玻璃球镂空技术,如有忘记,请移步此处

先看效果

光照案例

                          图7-1

Cornell box案例(最初步)

    图7-2

正文

学了光照就迫不及待地整了一堆东西

终于脱开了蓝色插值背景转到正儿八经的光了

在还没学长方形之前,就先用球体做了光源

注:坐标轴按照光线追踪坐标系描述(y轴位于垂直向上方向,z轴垂直屏幕向外)

1. 图7-1 第二行左

该图是最开始的一张图,相机仍然在(13,3,2),第一卦限

而球体是一个半径为1的漫反射白球,置于原点处

下面仍然是一个大的镜面球(metal),y轴-1000处,半径999,正好和小球相切

红色灯光则置于第三卦限,例如:(-3,3,-3)

整个场景的背景为黑色,即光线路径计算失败后返回黑色

如上,则会看到球体表面有一抹红色的色泽,然后大球镜面反射也有一部分

        图7-3

2. 图7-1 第二行中

在上图的基础上添加一个位于第四卦限的蓝色光源

就会形成漫反射球左侧为蓝色表面光泽右侧为红色表面光泽的效果

3. 图7-1 第一行左

上面两个当然很没意思了,但是一直以来都是蓝色背景亮堂堂的,第一次黑不溜秋的地方用灯照着东西,感觉挺真实的,光线追踪效果也很不错,所以上面两张图是新世纪的开端!

我们在(0,0,2)处,放一个半径为1的镜面球,在原点对称处放一个半径为1的玻璃球

下面的大球改为漫反射

哇,想想就刺激,结果不出所料

镜面球在黑乎乎的环境下只映出了蓝色光源和红色光源,以及旁边的漫反射球的相关部分,而玻璃球就更有意思了,透了红光照在漫反射大球表面上,还透了微弱的蓝光,也照在了右侧的地面上

4. 图7-1 第一行中

突然想到一个绝妙的主意,玻璃球可以镂空

于是设置了一个镂空球(0,0,-2),半径为-0.8

之后,想着把镜面球和磨砂小球离远一点,再观察磨砂小球在镜面球中的影,于是乎就成了上面这张图

镜面球依旧映这磨砂小球和灯光的影,然而玻璃球只有上面一丝丝的明亮,着实看着不尽人意

可能是镂空的太多了,于是有了右边那张图

5. 图7-1 第一行右

把镂空球半径设置小一点,想了想就-0.2吧

果然,不负吾望,还真是着实好看,不仅可以往地上透光,形状更有意思,像个立体环!!

其实,我是想调一个把左边两个特点合二为一的图,即既有第一张图的底面透光,又有第二图上表面那个明亮的高光

6. 图7-1 第二行右

其实是为了凑齐6张图,思来想去,没啥整的了,老是调个镂空半径没啥意思,渲染时间还长,后来想了下,不如把大球改成原来的镜面,这样下面三张图都是镜子大地,上面三张都是磨砂大地

于是乎,emmm,貌似还完成了上面提到的梦想,上表面”高光”以及底面的透光,不仅如此,而且镂空内表面还有透光,还映在了大地上,强无敌嘞~

上述场景代码

intersect* light()
{
    texture * perlintex = new noise_texture(6.3);
    material* redlight = new areaLight(new constant_texture(rtvec(0.98, 0.1, 0.08)));
    material* bluelight = new areaLight(new constant_texture(rtvec(0.05, 0.05, 1.)));

    intersect**list = new intersect*[7];
    list[0] = new sphere(rtvec(-2, 3, -3), 1.5, redlight);
    list[1] = new sphere(rtvec(-2.2, 3.2, 2.8), 1.5, bluelight);
    list[2] = new sphere(rtvec(0, 0, 2.2), 1, new metal(new constant_texture(rtvec(1, 1, 1))));
    list[3] = new sphere(rtvec(), 1, new lambertian(new constant_texture(rtvec(1, 1, 1))));
    list[4] = new sphere(rtvec(0, 0, -2), 1, new dielectric(1.5));
    list[5] = new sphere(rtvec(0, 0, -2), -0.18, new dielectric(1.5));
    list[6] = new sphere(rtvec(0, -1000, 0), 999,
        new dielectric(1.5));
    return new intersections(list, 7);
}

Chapter 7:Instance

我们来进行正常的章节学习,emmmm

现在先来学习轴平行的长方体,这个东西呢我知道的目前有两种方法

第一种是球坐标系下多个方位角和长宽高参数确定的长方体

此图引用于https://blog.csdn.net/libing_zeng/article/details/54561605

如果想要学习的话可以去学习一下这种方法

第二种方法自然就是顶点确定形体,左下-右上顶点确定形体,不仅适用于2D的形,同样适用于3D的体

心里罗列一下我们现有的零件,是否能够整一个长方体出来,好像可以

我们已经弄好了长方形,那么就可以粘成长方体

不错,只要把各个长方形的法线指向外部即可,而第一种方法也要求取每个面的法线

所以,我们这种方法还是比较好的,毕竟我们就是用6个法线构建的,第一种还需要方位角转换运算求取

那么我们就写成了如下代码

/// box.hpp

// -----------------------------------------------------
// [author]        lv
// [begin ]        2019.1
// [brief ]        the box-class for the ray-tracing project
//                from the 《ray tracing the next week》
// -----------------------------------------------------

#pragma once

namespace rt
{

// the statement of box class

class box: public intersect
    {
public:
    box() {  }

    box(const rtvec& pointmin, const rtvec& pointmax, material * mat);

    virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const override;

    virtual aabb getbox()const override;

private:
    rtvec _min;

    rtvec _max;

    intersect* _list;
    };

// the implementation of box class

inline     box::box(const rtvec& pointmin, const rtvec& pointmax, material * mat)
    :_min(pointmin)
    ,_max(pointmax)
    {
    intersect ** list = new intersect*[6];
    list[0] = new xy_rect(_min.x(), _max.x(), _min.y(), _max.y(), _max.z(), mat);
    list[1] = new flip_normal(new xy_rect(_min.x(), _max.x(), _min.y(), _max.y(), _min.z(), mat));
    list[2] = new xz_rect(_min.x(), _max.x(), _min.z(), _max.z(), _max.y(), mat);
    list[3] = new flip_normal(new xz_rect(_min.x(), _max.x(), _min.z(), _max.z(), _min.y(), mat));
    list[4] = new yz_rect(_min.y(), _max.y(), _min.z(), _max.z(), _max.x(), mat);
    list[5] = new flip_normal(new yz_rect(_min.y(), _max.y(), _min.z(), _max.z(), _min.x(), mat));
    _list = new intersections(list, 6);
    }

bool box::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const
    {
    return _list->hit(sight, t_min, t_max, info);
    }

aabb box::getbox()const
    {
    return aabb(_min, _max);
    }

} // rt namespace 

根据最小点坐标和最大点坐标构建六个面

于是我们来做开篇第二张图

在上一篇文章的Cornell box的场景中添加上面两个box

如果你的代码中,上一篇的仍然是背景为白色(即光线路径计算失败后返回白色)

那么将是下面这个

      图7-4

面向我们的两个物体面是光线追踪几乎计算不到的地方,所以基本是纯白色

我们迫不得已再把背景改为黑色

如第34行所示

但是我们一想到上一篇的一堆黑点噪声就。。。真是把一张美图糟蹋了

      图7-5

如何优化呢?

思来想去,有下列几种方法

1. 把区域光面积调大

2. 把光源与顶部距离调大,因为房间的每一面墙壁都是边长为555的正方形,敢问,距离为一个像素的光如何把偌大的平面照亮,于是乎,我改成了距离5....

3. 相机距离房间门口800像素,我们调为700像素

则修改后的图像为:

      图7-6

还有一个最重要的改进方式,增加采样点,即增加光线条数

可以对比,sample为10的时候(之前是sample为100)

    图7-7

从《Ray Tracing From the Ground Up》中得知,最简单粗暴的方法是发出万条光线做路径计算可以得到我们想要的图片

于是我将sample改为了2w,跑了一夜,现在是这样的

      图7-8

可以看出来是相当清晰了

书中还提到了,对光线路径和光源本身同时进行采样计算的直接光照和间接光照结合方法优化画质,比上述的暴力法效率更好

但是目前不会对光源进行采样计算以及间接光照相关技术,所以不能为大家提供代码和效果

好了,我们继续章节学习——旋转和平移

我们知道,平移比较简单,但是在光线追踪中如何实现物体平移呢?

它并没有顶点集合,它只有一个几何体方程以及碰撞检测,怎么平移呢

对了,就是碰撞检测这里!

我们对每一个碰撞点进行变换计算,也就把整个理想化的物体实例化且做了变换

1. 平移

对于平移,我们可以对每个碰撞点进行移动也可以在计算碰撞点的时候把eye往反方向移动,进而,求取碰撞点,也可以实现平移

我们采取第二种

/// translate.hpp

// -----------------------------------------------------
// [author]        lv
// [begin ]        2019.1
// [brief ]        the translate-class for the ray-tracing project
//                from the 《ray tracing the next week》
// -----------------------------------------------------

#pragma once

namespace rt
{

class translate :public intersect
    {
public:
    translate(intersect* p, const rtvec& offset);

    virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const override;

    virtual aabb getbox()const override;

private:
    intersect* _item;

    rtvec _offset;

    };

translate::translate(intersect* p, const rtvec& offset)
    :_item(p)
    , _offset(offset)
    {
    }

bool translate::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const
    {
    ray movedRay(sight.origin() - _offset, sight.direction(), sight.time());
    if (_item->hit(movedRay, t_min, t_max, info))
        {
        info._p += _offset;
        return true;
        }
    return false;
    }

aabb translate::getbox()const
    {
    aabb box = _item->getbox();
    return aabb(box.min() + _offset, box.max() + _offset);
    }

}// rt namespace

这个比较简单

2. 旋转

我们来复习一下图形学中仿射变换的知识

关于旋转:(引用书上一张图)

x‘ = cosθ * x - sinθ * y
y‘ = sinθ * x + cosθ * y

那么写成惯用的矩阵形式(采用列向量表示法),则是(绕z轴转)

同理,绕y轴转:

绕x轴转:

那么,我们来写绕y轴转的类

/// rotate.hpp

// -----------------------------------------------------
// [author]        lv
// [begin ]        2019.1
// [brief ]        the rotate-class for the ray-tracing project
//                from the 《ray tracing the next week》
// -----------------------------------------------------

#pragma once

namespace rt
{

// the statement of rotate class

class rotate :public intersect
    {
public:
    rotate(intersect* p, rtvar angle);

    virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const override;

    virtual aabb getbox()const override;

private:
    intersect* _item;

    rtvar _sinθ;

    rtvar _cosθ;

    aabb _box;

    };

// the implementation of rotate class

rotate::rotate(intersect* p, rtvar angle)
    :_item(p)
    {
    rtvar radians = (π / 180.) * angle;
    _sinθ = sin(radians);
    _cosθ = cos(radians);
    rtvec min(rtInf(), rtInf(), rtInf());
    rtvec max = -min;
    for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 2; ++j)
            for (int k = 0; k < 2; ++k)
                {
                rtvar x = i * _box.max().x() + (1 - i)*_box.min().x();
                rtvar y = j * _box.max().y() + (1 - j)*_box.min().y();
                rtvar z = k * _box.max().z() + (1 - k)*_box.min().z();
                rtvar newx = _cosθ * x + _sinθ * z;
                rtvar newz = -_sinθ * x + _cosθ * z;
                rtvec tester(newx, y, newz);
                for (int c = 0; c < 3; ++c)
                    {
                    if (tester[c] > max[c])
                        max[c] = tester[c];
                    if (tester[c] < min[c])
                        min[c] = tester[c];
                    }
                }
    _box = aabb(min, max);
    }

bool rotate::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const
    {
    rtvec eye = sight.origin();
    rtvec direction = sight.direction();
    eye[0] = _cosθ * sight.origin()[0] - _sinθ * sight.origin()[2];
    eye[2] = _sinθ * sight.origin()[0] + _cosθ * sight.origin()[2];
    direction[0] = _cosθ * sight.direction()[0] - _sinθ * sight.direction()[2];
    direction[2] = _sinθ * sight.direction()[0] + _cosθ * sight.direction()[2];
    ray rotatedRay(eye, direction, sight.time());
    if (_item->hit(rotatedRay, t_min, t_max, info))
        {
        rtvec p = info._p;
        rtvec n = info._n;
        p[0] = _cosθ * info._p[0] + _sinθ * info._p[2];
        p[2] = -_sinθ * info._p[0] + _cosθ * info._p[2];
        n[0] = _cosθ * info._n[0] + _sinθ * info._n[2];
        n[2] = -_sinθ * info._n[0] + _cosθ * info._n[2];
        info._p = p;
        info._n = n;
        return true;
        }
    return false;
    }

aabb rotate::getbox()const
    {
    return _box;
    }

} // rt namespace

我们来写图7-7的场景

intersect* Cornell()
{
    intersect ** list = new intersect*[9];
    size_t cnt = 0;
    material * red = new lambertian(new constant_texture(rtvec(0.65, 0.05, 0.05)));
    material * blue = new lambertian(new constant_texture(rtvec(0.05, 0.05, 0.73)));
    material * white = new lambertian(new constant_texture(rtvec(0.88, 0.88, 0.88)));
    material * green = new lambertian(new constant_texture(rtvec(0.12, 0.45, 0.15)));
    material * light = new areaLight(new constant_texture(rtvec(20, 20, 20)));

    list[cnt++] = new flip_normal(new yz_rect(0, 555, 0, 555, 555, green));
    list[cnt++] = new yz_rect(0, 555, 0, 555, 0, red);
    list[cnt++] = new xz_rect(200, 350, 220, 340, 550, light);
    list[cnt++] = new flip_normal(new xz_rect(200, 350, 220, 340, 550, light));
    list[cnt++] = new flip_normal(new xz_rect(0, 555, 0, 555, 555, white));
    list[cnt++] = new xz_rect(0, 555, 0, 555, 0, white);
    list[cnt++] = new flip_normal(new xy_rect(0, 555, 0, 555, 555, blue));

    list[cnt++] = new translate(new rotate(new box(rtvec(), rtvec(165, 165, 165), white), -18), rtvec(130, 0, 65));
    list[cnt++] = new translate(new rotate(new box(rtvec(), rtvec(165, 330, 165), white), 15), rtvec(265, 0, 295));

    return new intersections(list, cnt);
}

图7-8是图7-7的高清版,暂时还没跑完,渲染完之后我在此处放上此场景的高清版,以及任意轴旋转的扩充代码

敬请期待。。。

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

原文地址:https://www.cnblogs.com/lv-anchoret/p/10307569.html

时间: 2024-11-06 03:54:34

【Ray Tracing The Next Week 超详解】 光线追踪2-7 任意长方体 && 场景案例的相关文章

【Ray Tracing The Next Week 超详解】 光线追踪2-6 Cornell box

Chapter 6:Rectangles and Lights 今天,我们来学习长方形区域光照  先看效果 light 首先我们需要设计一个发光的材质 /// light.hpp // ----------------------------------------------------- // [author] lv // [begin ] 2019.1 // [brief ] the areaLight-class for the ray-tracing project // from t

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

<Ray Tracing in One Weekend>完结篇 最近课程上机实验,封面图渲染时间也超长,所以写东西就落下了,见谅 这篇之后,我会继续<Ray Tracing The Next Week>,还请多多关注 这几天我在渲染这本书的封面图,封面图还没出,不算结束,刚好安排了10节 今天呢,有两件事: 1.阐述整个工程的文件组织即内容 2.阐述封面,完结 12.1工程文件组织 试过很多方法,问过很多老师,无奈,子类继承实现的父类纯虚函数实在无法和类声明分成两个文件(即声明放于

【Ray Tracing The Next Week 超详解】 光线追踪2-3

 Preface 终于到了激动人心的纹理章节了 然鹅,看了下,并不激动 因为我们之前就接触过 当初有一个 attenuation 吗? 对了,这就是我们的rgb分量过滤器,我们画出的红色.蓝色.绿色等等,都是通过它来控制的 专业点的词语叫做rgb衰减比例,比如rtvec(1.,0.,0.),最后呈现出来的是红色,因为r保留了100% 它是怎么控制的呢,我们来回顾一下这个过程 首先,我们创建一个材质球 后面那个rtvec(0.4,0.2,0.1)就是衰减比例(衰减到原来的百分之..) 之后 进入数

【Ray Tracing in One Weekend 超详解】 光线追踪1-7 Dielectric 半径为负,实心球体镂空技巧

今天讲这本书最后一种材质 Preface 水,玻璃和钻石等透明材料是电介质.当光线照射它们时,它会分裂成反射光线和折射(透射)光线. 处理方案:在反射或折射之间随机选择并且每次交互仅产生一条散射光线 (实施方法:随机取样,具体见后文) 调试最困难的部分是折射光线.如果有折射光线的话,我通常首先让所有的光折射.对于这个项目,我试图在我们的场景中放置两个玻璃球,我得到了这个:   上述图片是对的吗?显然,在实际生活中,那两个玻璃球看起来怪怪的,实际情况下,里面的内容应该将现在的进行上下颠倒,且没有黑

POJ 1659 Frogs&#39; Neighborhood(可图性判定—Havel-Hakimi定理)【超详解】

Frogs' Neighborhood Time Limit: 5000MS   Memory Limit: 10000K Total Submissions: 9897   Accepted: 4137   Special Judge Description 未名湖附近共有N个大小湖泊L1, L2, ..., Ln(其中包括未名湖),每个湖泊Li里住着一只青蛙Fi(1 ≤ i ≤ N).如果湖泊Li和Lj之间有水路相连,则青蛙Fi和Fj互称为邻居.现在已知每只青蛙的邻居数目x1, x2, ..

CentOS6启动过程超详解分析

CentOS 6 开机流程--linux由kernel和rootfs组成.kernel负责进程管理.内存管理.网络管理.驱动程序.文件系统.安全等;rootfs由程序和glibc组成,完善操作系统的功能.同时linux内核的特点是模块化,通过对模块装载卸载可以对内核功能自定义.linux内核文件:/boot/vmlinuz-2.6.32-696.el6.x86_64 整体的流程 BIOS/开机自检 MBR引导(Boot Loader) 启动内核 启动第一个进程init 一.BIOS/开机自检 1

【RAY TRACING THE REST OF YOUR LIFE 超详解】 光线追踪 3-5 random direction &amp; ONB

 Preface 往后看了几章,对这本书有了新的理解 上一篇,我们第一次尝试把MC积分运用到了Lambertian材质中,当然,第一次尝试是失败的,作者发现它的渲染效果和现实有些出入,所以结尾处声明要通过实践,改进当前的效果 于是乎,就有了后面的章节,几乎整本书都在讲,如何一步一步地改进上一篇的画质,使其更加符合现实,上一篇其实是抛砖引玉 这本书的小标题名为the rest of your life 通过前面几章,我们可以更好地理解这句话:我们通过MC积分优化效果,采用的是pdf函数,之前说过,

高斯消元法(Gauss Elimination)【超详解&amp;模板】

高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵.高斯消元法的原理是:若用初等行变换将增广矩阵 化为 ,则AX = B与CX = D是同解方程组. 所以我们可以用初等行变换把增广矩阵转换为行阶梯阵,然后回代求出方程的解. 1.线性方程组 1)构造增广矩阵,即系数矩阵A增加上常数向量b(A|b) 2)通过以交换行.某行乘以非负常数和两行相加这三种初等变化将原系统转化为更简单的三角形式(triangular form) 注:这里的初等变化可以通过

海量数据处理算法总结【超详解】

1. Bloom Filter [Bloom Filter]Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合.它是一个判断元素是否存在集合的快速的概率算法.Bloom Filter有可能会出现错误判断,但不会漏掉判断.也就是Bloom Filter判断元素不再集合,那肯定不在.如果判断元素存在集合中,有一定的概率判断错误.因此,Bloom Filter不适合那些“零错误”的应用场合. 而在能容忍低错误率的应用场合