概述

本文主要记录我在跟进 RayTracingInOneWeekend - The Book Series[1]代码过程中,产生的思考与遇到的问题的记录与回答。

RayTracingInOneWeekend

项目是怎么将场景渲染转为图片的?

PPM格式输出。PPM记录每一个像素的RGB信息,通过头部明文确认长宽,按行存储。对于输出部分,使用我们最熟悉的cout作为输出流:

std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";
for(){
    for(){
    std::cout << ir << ' ' << ig << ' ' << ib << '\n';
          }
     }

对于编译为.exe的文件,我们使用 .\RayTracing.exe > image.ppm,就可以将输出流记录到这个文件里。

ASCII格式

P3
300 168
255
220 235 255
220 235 255
220 235 255
...

另外其实还有二进制格式P6。

如果想要实现写入其他格式如JPG,可以尝试一些库,例如 stb_image_write.h 。一般而言不使用输出流,而是分配数组写入,经由库文件处理,执行.exe文件时自动生成图像。

RayTracingTheNextWeek

实现的快门动态模糊到底是什么原理?

  • 快门时间决定时间范围,我们对发出光线随机一个时间点∈[快门时间],这条光线记录这个时间点的颜色信息。
  • 一个像素发射多条光线,某些会捕捉到物体位于该像素的时刻返回强颜色,另一些则返回背景色,最终返回颜色的加权平均,从而形成半透明效果。

也就是说极端来讲,不管快门多长,存在一种情况所有光线都被随机到同一个时间点,这个时候就不存在动态模糊的物体了。

根据大数定律,极端情况概率趋近于零所有光线时间戳完全相同的概率为 \( \left( \frac{1}{\text{time1} - \text{time0}} \right)^N \)(\( N = \text{采样数} \)),实际渲染中采样数相当高,几乎不可能发生。

为什么BVH要选择最长轴作为分割轴?

最大程度减少子树包围盒重叠。

BVH构建的基本流程为:选择分割轴→沿轴排序物体→按某种规则将物体分为左右子树

随机选择分割轴,可能导致树结构不平衡:某些子树过深或浅、包围盒重叠低效:增加光线与多个子树求交概率。如图,这是沿X轴放置的两个重叠的方块:

如果沿X轴(最长轴)进行排序,就是先读到红色区域,再到紫色,再到蓝色,可以有效识别。
而如果是沿着Y/Z轴,则全程都接触到3个区域,就会造成BVH退化,最终使得光线需要与整个包围盒内物体求交。

// int axis = random_int(0,2);//随机轴可能导致BVH退化
int axis = bbox.longest_axis();

在应用了上面的算法后,对300x168、100采样、50深度、16线程的渲染,耗时可从15s降至11s。

不过这种简单的做法也有个例外:还是按上图的排布,但物体在Y/Z的长度要长于X轴排布长度,那这个方法就会改而选择Y/Z轴为分割轴,此时就失效了。实际工程中,会结合SAH(Surface Area Heuristic)等更复杂的策略来优化分割。

引用

[1] Ray Tracing in One Weekend - The Book Series