上一章我们引入包围盒的概念后,大大缩短了我们运行时间,但整体逻辑开始变的复杂起来,没关系,让我们来一起梳理一下之前的内容。
回顾
首先我们把man函数当成一个raycast类,他除了需要一些长宽比,分辨率、采样数、反射深度等变量外,还需要一个自由相机、一个bvh_node构成的世界,有了这些,它就可以给我们呈现一张”美丽”的图片了,这其中两个关键就是相机和世界了。
先来看看相机,它由很多参数构造而成,但构造完成后,我们就不必管那么多,我们只需要在main函数传入一个0-1内的u、v值就可以得到我们想到的光线,再由ray_color函数来获得该光线带回的颜色。ray_color函数只需要光线,世界和深度就可以得到颜色,这个过程就是所谓的碰撞。
world是bvh_node类型,由一个hittable_list和两个时间构造而成,在构造函数中,对物体按随机轴进行二分直到只有一个物体或物体列表,为它们套上各自的包围盒后,回溯为每个节点的左右孩子一起套上更大的包围盒。目前的这种方式也代表了虽然我们可以在hitable_list里面包含hittable_list但是该函数并不会打开列表将里面的物体拿出来进行二分,所以当下为了更快的速度不建议这么做。
再回到我们的ray_color函数,光线首先会world这个最大包围盒检测,如果通过,继续检测左右孩子,当所有包围盒检测都通过之后,那么它便会和我们具体物体进行碰撞检测,由于我们的bvh的排序仅仅是通过比较每个节点最小值得到,所以是可能同时通过所有检测来到多个不同的物体进行碰撞,所以hit函数中深度检测也是必不可少的一环,将碰撞带回反射率和新的光线带回的颜色相乘就得到了这一次采样的结果,上述过程是一个递归的过程,反射光线会再一次与世界”碰撞“,直到反射的光线什么都没有碰到或者是碰到了光源。
这里还有一个非常重要的点,就是怎么得到的新的反射(折射)光线呢?这就是材质存在的意义了,每个物体中都包含一个材质,通过hit函数调用材质内scatter方法得到就可以得到一根新的光线!
将这样的采样重复一定次数取平均就得到这了这个像素点的颜色了,填满它们就得到我们的图片了。为了更加方便的理解这里放上他们的类图:
最后谈谈我们是如何管理这个world,一切还得从hittable说起,它有我们最重要的碰撞函数hit,以及刚刚引入的bounding_box包围盒生成函数,还有四个继承者分别是球、移动球、物体列表以及bvh_node,继承者们自然需要实现基类的各自的碰撞及包围盒函数。
让我们来看看这些继承者们,首先最简单球和移动球,他们是抽象模型中最底层最具体的类,无论上层如何处理,最后的任务都要落到他们的头上,当然以后也会增加更多的物体进行扩充,物体对hittable的继承也让我们可以通过容器统一进行管理实现多态,这种设计方式也非常符合面向对象的设计思想,及应该尽可能的与抽象类互动而不是底层的派生类,从而提高代码的可复用性。
第三个继承者物体列表可以看成一个大团队,bvh_node那就可以理解为经理,给他一个团队,经理给团队下的人排序编号,当有任务来时,就看看适合哪个部门的哪个编号的人(球),如果底下的人(球)都干不了就直接放弃这单,合适就交给对应的人(球)去处理,这样公司就运作起来了,不对是光线就可以找到对应的物体进行碰撞了。
拓展
- 创建空类图,再脑内模拟图片生成过程,补全类图。