本项目上接《用两天学习光线追踪》,继续学习光线追踪。
项目链接:https://github.com/maijiaquan/ray-tracing-with-imgui

目录:
《用两天学习光线追踪》1.项目介绍和ppm图片输出
《用两天学习光线追踪》2.射线、简单相机和背景输出
《用两天学习光线追踪》3.球体和表面法向量
《用两天学习光线追踪》4.封装成类
《用两天学习光线追踪》5.抗锯齿
《用两天学习光线追踪》6.漫反射材质
《用两天学习光线追踪》7.反射向量和金属材质
《用两天学习光线追踪》8.折射向量和电介质
《用两天学习光线追踪》9.可放置相机
《用两天学习光线追踪》10.散焦模糊

《用一周学习光线追踪》1.动态模糊
《用一周学习光线追踪》2.BVH树、AABB相交检测
《用一周学习光线追踪》3.纯色纹理和棋盘纹理
《用一周学习光线追踪》4.柏林噪声
《用一周学习光线追踪》5.球面纹理贴图
《用一周学习光线追踪》6.光照和轴对齐矩形
《用一周学习光线追踪》7.长方体和平移旋转


本节内容

射线和物体的相交检测,是光线追踪器的主要时间瓶颈,目前时间复杂度是线性的。有两种方法可以将时间复杂度降低到对数级别:一是空间划分,二是物体划分。本节将用BVH实现物体划分,对当前的项目进行加速。


层次包围体 BVH

Bounding volume hierarchy (BVH) 层次包围体

摘自BVH维基百科:在BVH中,所有的几何物体都会被包在bounding volume的叶子节点里面,bounding volume外面继续包着一个更大的bounding volume,递归地包裹下去,最终形成的根节点会包裹着整个场景。常用于碰撞检测和光线追踪的加速,能够将时间复杂度降低到关于物体数量的对数级别。

举例:找个volume包围10个物体,如果射线没击中这个volume,那么射线肯定没击中这10个物体。
注意:把物体分成子集,可能会bounding volums重叠的情况。
例如用方框来包围物体,一堆物体被分成红色和蓝色两组,这两组物体又被更大的紫色方框包围。

伪代码如下:

if (hits purple)hit0 = hits blue enclosed objectshit1 = hits red enclosed objectsif (hit0 or hit1)return true and info of closer hit
return false

最常见的bounding volume就是AABB。


轴对齐包围盒 AABB

轴对齐包围盒:axis-aligned bounding box, 缩写为 AABB

可用slab方法检测射线于AABB相交。

下图是一个2D的AABB:

下图是射线在x方向上的相交情况:

p(t)=A+t⋅Bp(t) = A + t \cdot Bp(t)=A+t⋅B

x0=Ax+t0⋅Bxx_0 = A_x + t_0 \cdot B_xx0​=Ax​+t0​⋅Bx​

则x轴方向上的t区间为:
t0=x0−AxBxt_0 = \frac{x_0 - A_x}{B_x} t0​=Bx​x0​−Ax​​

t1=x1−AxBxt_1 = \frac{x_1 - A_x}{B_x} t1​=Bx​x1​−Ax​​

射线和AABB是否相交,可以转化为不同轴方向上的t区间是否重叠。

如下图绿色区间和蓝色区间。

2D情况写成伪代码:

compute (tx0, tx1)
compute (ty0, ty1)
return overlap?( (tx0, tx1), (ty0, ty1))

3D情况写成伪代码:

compute (tx0, tx1)
compute (ty0, ty1)
compute (tz0, tz1)
return overlap?( (tx0, tx1), (ty0, ty1), (tz0, tz1))

注意2个问题:
1.射线沿着x轴的反方向发射,区间会反过来,例如(7,3)
2.分母可能为0

tx0=x0−AxBxt_{x0} = \frac{x_0 - A_x}{B_x} tx0​=Bx​x0​−Ax​​

tx1=x1−AxBxt_{x1} = \frac{x_1 - A_x}{B_x} tx1​=Bx​x1​−Ax​​

BxB_xBx​为0的情况,tx0t_{x0}tx0​ 和 tx1t_{x1}tx1​都会是正无穷或者负无穷,tx0t_{x0}tx0​ 和 tx1t_{x1}tx1​可以分别取两者之间的较小者和较大者。

tx0=min(x0−AxBx,x1−AxBx)t_{x0} = min(\frac{x_0 - A_x}{B_x}, \frac{x_1 - A_x}{B_x}) tx0​=min(Bx​x0​−Ax​​,Bx​x1​−Ax​​)

tx1=max(x0−AxBx,x1−AxBx)t_{x1} = max(\frac{x_0 - A_x}{B_x}, \frac{x_1 - A_x}{B_x}) tx1​=max(Bx​x0​−Ax​​,Bx​x1​−Ax​​)

问题2:Bx=0B_x = 0Bx​=0 的同时,x0−Ax=0x_0 - A_x = 0x0​−Ax​=0 或 x1−Ax=0x_1-A_x= 0x1​−Ax​=0,这个问题稍后再处理。

假设没有反转区间,只需处理两对t区间是否重叠,伪代码如下:

bool overlap(d, D, e, E)f = max(d, e) //f为两者起点的最大值F = min(D, E)    //F为两者终点的最小值return (f < F)   //区间相交条件:f<F

f为两者起点的最大值,F为两者终点的最小值,区间相交条件:f<F。
可参考下图理解:

3D的情况,则需要多判断一次,写成代码如下:

inline float ffmin(float a, float b) { return a < b ? a : b; }
inline float ffmax(float a, float b) { return a > b ? a : b; }class aabb {public:vec3 _min; //左下角顶点vec3 _max; //右上角顶点aabb() {}aabb(const vec3& a, const vec3& b) { _min = a; _max = b;}vec3 min() const {return _min; }vec3 max() const {return _max; }//判断射线是否在t区间bool hit(const ray &r, float tmin, float tmax) const{for (int a = 0; a < 3; a++){float invD = 1.0f / r.direction()[a];float t0 = (min()[a] - r.origin()[a]) * invD;float t1 = (max()[a] - r.origin()[a]) * invD;if (invD < 0.0f)std::swap(t0, t1);tmax = t1 < tmax ? t1 : tmax;   //F为两者终点的最小值tmin = t0 > tmin ? t0 : tmin;   //f为两者起点的最大值if (tmax <= tmin)       //F <= freturn false;}return true;}
};

此外我们还需要计算2个box的包围盒:

//计算2个box的包围盒
aabb surrounding_box(aabb box0, aabb box1)
{vec3 small(ffmin(box0.min().x(), box1.min().x()),ffmin(box0.min().y(), box1.min().y()),ffmin(box0.min().z(), box1.min().z()));vec3 big(ffmax(box0.max().x(), box1.max().x()),ffmax(box0.max().y(), box1.max().y()),ffmax(box0.max().z(), box1.max().z()));return aabb(small, big);
}

用bounding_box来设置物体的包围盒,目前场景中的物体只有sphere,但后续会新增其他物体(例如无边界的平面),这些物体不一定都会有包围盒,所以该函数返回bool值。对于动态模糊球体moving_sphere则需要时间间隔参数来模拟相机快门曝光,所以参数里面带上t0和t1。
在基类hittable中定义虚函数bounding_box(),并在子类中分别实现:

class hittable {public:...virtual bool bounding_box(float t0, float t1, aabb& box) const = 0;
};bool moving_sphere::bounding_box(float t0, float t1, aabb &box) const
{aabb box0(center(t0) - vec3(radius, radius, radius), center(t0) + vec3(radius, radius, radius));aabb box1(center(t1) - vec3(radius, radius, radius), center(t1) + vec3(radius, radius, radius));box = surrounding_box(box0, box1);return true;
}bool sphere::bounding_box(float t0, float t1, aabb &box) const
{box = aabb(center - vec3(radius, radius, radius), center + vec3(radius, radius, radius));return true;
}bool hittable_list::bounding_box(float t0, float t1, aabb &box) const
{if (list_size < 1)return false;aabb temp_box;bool first_true = list[0]->bounding_box(t0, t1, temp_box);if (!first_true)return false;elsebox = temp_box;for (int i = 1; i < list_size; i++){if (list[i]->bounding_box(t0, t1, temp_box)){box = surrounding_box(box, temp_box);}elsereturn false;}return true;
}

BVH树的实现

下面开始实现BVH树,定义BVH树的节点为bvh_node类,继承自hittable。目前hittable子类的继承关系如下:

sphere
hittable
moving_sphere
hittable_list
bvh_node

每个bvh_node都有一个AABB和左右孩子指针,左右孩子同样指向hittable的指针,可能指向bvh_node,也可能指向sphere或其他hittable子类。

hit()函数非常简单粗暴:检查当前节点的box是否命中,若是,则检查该节点的左右孩子(可能是bvh_node或sphere)是否命中。

对于所有的加速结构(包括BVH),最复杂的部分就是创建。使用自顶向下创建BVH树的方式如下:

1.随机选择x,y,z轴中的一个
2.对该轴正方向的顺序对物体排序,并分成前后两半
3.前一半物体放左子树,后一半物体放右子树,继续对左右子树递归创建。如果左右子树的物体数量等于2,直接左右节点分别指向物体。如果等于1,则左右节点均指向该物体。最后将当前节点的aabb设置为包含左右子树的box。

class bvh_node : public hittable
{public:bvh_node() {}hittable *left;hittable *right;aabb box;bool bounding_box(float t0, float t1, aabb &b) const{b = box;return true;}bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const{if (box.hit(r, t_min, t_max)){hit_record left_rec, right_rec;bool hit_left = left->hit(r, t_min, t_max, left_rec);bool hit_right = right->hit(r, t_min, t_max, right_rec);if (hit_left && hit_right){if (left_rec.t < right_rec.t)rec = left_rec;elserec = right_rec;return true;}else if (hit_left){rec = left_rec;return true;}else if (hit_right){rec = right_rec;return true;}elsereturn false;}elsereturn false;}bvh_node(hittable **l, int n, float time0, float time1){int axis = int(3 * random_double());if (axis == 0)qsort(l, n, sizeof(hittable *), box_x_compare);else if (axis == 1)qsort(l, n, sizeof(hittable *), box_y_compare);elseqsort(l, n, sizeof(hittable *), box_z_compare);if (n == 1){left = right = l[0];}else if (n == 2){left = l[0];right = l[1];}else{left = new bvh_node(l, n / 2, time0, time1);right = new bvh_node(l + n / 2, n - n / 2, time0, time1);}aabb box_left, box_right;if (!left->bounding_box(time0, time1, box_left) ||!right->bounding_box(time0, time1, box_right)){std::cerr << "no bounding box in bvh_node constructor\n";}box = surrounding_box(box_left, box_right);}
};

x轴上的qsort()比较函数如下:

int box_x_compare (const void * a, const void * b) {aabb box_left, box_right;hittable *ah = *(hittable**)a;hittable *bh = *(hittable**)b;if (!ah->bounding_box(0,0, box_left) || !bh->bounding_box(0,0, box_right))std::cerr << "no bounding box in bvh_node constructor\n";if (box_left.min().x() - box_right.min().x() < 0.0)return -1;elsereturn 1;
}

同理可实现y轴和z轴的比较函数。

修改random_scene的返回值:

hittable *random_scene() {...// return new hittable_list(list,i);return new bvh_node(list, i, 0.0, 1.0);
}

测试结果

43s,比上一节的92s快了接近一倍。


参考资料:Ray Tracing: The Next Week

《用一周学习光线追踪》2.BVH树、AABB相交检测相关推荐

  1. libgdx学习记录27——线段与线段相交检测

    给定p1, p2, p3, p4四个点,p1,p2为一条线段,p3,p4为一条线段,检测其是否有交点. 可分为三种情况: 1. L2与x轴平行 2. L2与y轴平行 3. L2与坐标轴不平行. (L1 ...

  2. 《用两天学习光线追踪》1.项目介绍和ppm图片输出

    本项目参考自教程<Ray Tracing in One Weekend>,在跑通了所有例子之后,加上了自己的理解写成笔记,项目使用CPU多线程提速,并增加了GUI进度显示. 项目链接:ht ...

  3. 《用两天学习光线追踪》3.球体和表面法向量

    本项目参考自教程<Ray Tracing in One Weekend>,在跑通了所有例子之后,加上了自己的理解写成笔记,项目使用CPU多线程提速,并增加了GUI进度显示. 项目链接:ht ...

  4. 《用两天学习光线追踪》7.反射向量和金属材质

    本项目参考自教程<Ray Tracing in One Weekend>,在跑通了所有例子之后,加上了自己的理解写成笔记,项目使用CPU多线程提速,并增加了GUI进度显示. 项目链接:ht ...

  5. 【光线追踪系列十】光追加速结构(BVH树)

    本文主要参照 Ray Tracing: The Next Week,其中只是主要精炼光追相关理论,具体实现可参照原文. 经过之前几部分的光追实现,我们大致可以实现了光追的效果,但其中有一个致命的bug ...

  6. 空间数据结构(四叉树、八叉树、BVH树、BSP树、k-d树)

    转载地址:https://www.cnblogs.com/KillerAery/p/10878367.html 1. 前言: 在游戏程序中,利用空间数据结构加速计算往往是非常重要的优化思想,空间数据结 ...

  7. 空间数据结构(四叉树/八叉树/BVH树/BSP树/k-d树)

    转载说明: 原作者:KillerAery 出处:https://www.cnblogs.com/KillerAery/p/10878367.html 1 四叉树/八叉树 (Quadtree/Octre ...

  8. 20172328 2018-2019《Java软件结构与数据结构》第八周学习总结

    20172328 2018-2019<Java软件结构与数据结构>第八周学习总结 概述 Generalization 本周学习了二叉树的另一种有序扩展?是什么呢?你猜对了!ヾ(◍°∇°◍) ...

  9. 分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日)

    分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日) 本周Silverlight学习资源更新 Silverlight 定位 niejunhua [学习 ...

最新文章

  1. 【FFmpeg】windows下的库下载
  2. JavaScript 爆红后,微软为何还要开发 TypeScript?
  3. 1月28日云栖精选夜读 | 终于等到你!阿里正式向 Apache Flink 贡献 Blink 源码
  4. php文件不写结尾,php文件结尾不写?的好处有哪...-php判断两个日期相差天数的实...-php根据时间显示刚刚,几分钟前,几小时前的实现代码_169IT.COM...
  5. mysql too many connection解决方法
  6. 【评论】第一财经周刊:失败的埃洛普
  7. (一)appium-desktop定位元素原理
  8. 天巡:2018十一黄金周出境自由行大数据报告(附下载)
  9. Choco安装npm
  10. 服务器内存怎么看型号,怎么查看服务器内存的型号
  11. LiveData浅析
  12. 回顾日本和台湾的股市泡沫
  13. H.264基础知识总结
  14. navicat显示中文为问号
  15. 【蜂口 | AI人工智能】人脸美颜——龙鹏 深度学习与人脸图像应用连载(八)...
  16. java程序封装最小单位,持续更新中
  17. 复数中的运算符重载(续)
  18. VScode实现分屏显示浏览器(一边写前端代码一边可以实时查看结果)
  19. UML--协作图详解
  20. nvidia控制面板打不开解决方法教学

热门文章

  1. string 转换 LPSTR LPCSTR LPCWSTR
  2. Ubuntu-Thinkpade14无线网卡驱动安装
  3. 关于.H和.C的文章
  4. 7 DICOM成像协议编码实现-有损压缩和无损压缩解压
  5. 通达信网上交易接口的运行结果
  6. matlab的数字滤波器,基于Matlab的IIR数字滤波器设计方法比较及应用
  7. warning: CRLF will be replaced by LF in 出现的原因和解决办法
  8. Stata如何解决command is unrecognized?
  9. 凭借这份pdf,必拿下蚂蚁金服、字节跳动、小米等大厂的offer
  10. CES Asia专题|栩栩如生!Artec3D现场展示其3D扫描技术