传统相机的局限
现实中的相机可无法把它获取的所有光线都汇聚到一点,通过逆光路模型来解释,就是它无法精准的控制发射光线的起点都重合在一起。
如上图所示,inside表示相机内部,outside是相机的外部。
相机通过镜头采集光线,镜头采集到的光线会被汇总,在镜头后的胶片上成像。在镜头之外,根据镜头凹透镜的焦距,在某个距离处采集到的光线都来自于一点,这个平面上的所有物体都处于完美对焦状态,而其他距离下的物体,都会因为光线无法精准落于一点而出现模糊。离完美对焦距离越远,就越模糊。
模拟散焦模糊
在《抗锯齿》那一章我们编写的多采样求平均的算法就可以完美模拟从镜头采集到成像的过程,现在,只需要改变我们光线的发射位置,假装光线是从镜头上任意一点发射的即可。
具体要怎么做呢?
1.要如何模拟镜头呢?
我们可以以原来的固定光线发射点为圆心,找一个圆片,这个圆片和虚拟视口所在平面平行,和相机正对方向垂直。然后在这个圆片上随机找一个点,以这个点为起点发射光线(这个圆片就是虚拟相机的镜头)。这个圆片的大小对应着真实相机中的镜头光圈大小,圆片越大,散焦模糊效果就越大。
2.要如何模拟光线聚焦效果呢?
光线是射向虚拟视口上的虚拟像素内的。在我们原来的设计里,虚拟视口和相机位置的距离始终为1,这意味着,我们的光线都会在镜头前方距离为1的平面上聚焦,如果我们需要让他能在任意位置聚焦,我们只需要改变虚拟视口距离相机的距离即可。
还记得是如何在代码中规定虚拟视口的位置的吗?
1 | lower_left_corner = origin - horizontal/2 - vertical/2 - w; |
如果我想把它推到和相机位置距离为2的平面上,仅仅是修改w的系数就可以了吗?比如:
1 | lower_left_corner = origin - horizontal/2 - vertical/2 - 2*w; |
这样就足够了吗?不行,看下图:
图上描绘的是相机空间下VoW面上的截面,假设中间的绿色竖线就是原先的虚拟视口,它离相机的距离为1,现在要把它移动到右边的蓝色平面上,根据相似三角形,如果它离相机的距离改成2,那它的高度会同步扩大为2,且因为像素数量没变,每个虚拟像素的大小也会增加为2。这意味着horizontal和vertical也得同步进行放大。
图中射向虚拟视口的光线假设命中了第x行,如果像素的大小扩大两倍,这根光线的延长线也会命中蓝色视口的第x行。如果像素没有放大,它将命中第2x行,这就不对了。
更改相机代码,现在我们可以传入焦距和光圈大小:
1 | class camera { |
把random_in_unit_disk函数写在vec3.h类的类外,这个函数负责生成一个XoY面上以原点为圆心的圆片内的随机一点:
1 | //XoY面上以原点为圆心的圆片内的随机一点。 |
还是使用上一章的场景,但要调整相机,给一个很大的光圈直径,并且把对焦平面(虚拟视口)放在画面正中心的小球上:
1 | point3 lookfrom(3,3,2); |
会得到这张图,这里的光圈很大,只要物体离虚拟视口有一点距离,就看不清了。
最终成果
现在来汇总在第一章中学到的所有知识,绘制一张图片,这将会是集大成之作,写入代码:
1 | // 这个函数帮助生成一个丰富多彩的场景!!! |
可以给你的随机数生成器其他的种子,来改变小球的随机位置,直到你得到满意的结果。
到此为止,这本书的内容就结束喽!但这个渲染器并没有完成,接下来的内容在《Ray Tracing: The Next Week》中。
拓展
非常明显,高分辨率多个物体的图片是非常耗费时间的,想想如何充分利用CPU来给我们的程序提提速。
答案见下章。
参考文献
https://raytracing.github.io/books/RayTracingInOneWeekend.html
参考自《RayTracingInOneWeekend》第12节。