📄️ 1. 概览
我已经教授好几年图形学了。我通常通过实现一个光线追踪器来进行课程,因为这迫使学生自己写下所有代码。就算不调用 API,你仍可做出非常酷的图片。我决定将课程写成一个实战教程,带你尽快做出一个很酷的程序。虽然它不会是一个全功能光线追踪器,但其仍实现了间接光照 (间接光照使得光追成为电影中的常用技术)。跟随步骤,你会做出一个可拓展性极高的光线追踪器框架。若你对实现一个更进阶的光线追踪器感到兴趣,它应该非常容易改动。
📄️ 2. 输出图像
当你刚开始编写渲染器时,你应该想一个方法来显示图像。最直接的办法是将图像写入文件,但是图像文件格式实在是太多了。许多格式都非常复杂,所以我通常使用纯文本格式的 ppm 格式。这是 ppm 格式的 Wikipedia 介绍:
📄️ 3. vec3 類
几乎所有图形程序都有一些储存几何向量与颜色的类。在很多系统中,这些向量是四维的(几何中的三维位置加上一个齐次座标,或颜色中的 RGB 加上一个 Alpha 透明度分量)。为了我们的目的,三维座标便足够。我们将使用一样的 vec3 类来表示颜色、位置、方向、偏移量等等。有些人不喜欢这样,因为这无法预防你做出一些傻事,比如用一个颜色向量减去一个位置向量。他们的论点很对,但当没有明显错误时,我们将永远选择"代码最少"的路径。即便如此,我们的确为 vec3 声明了两个别名: point3 和 color 。由于这两种类型只是 vec3 的别名,如果你传递一个 color 给一个需要 point3 的函数,你不会得到任何警告,并且没有任何事会阻止你将 point3 和 color 相加,但这让代码更容易被阅读和理解一些。
📄️ 4. 光线、简单相机与背景
所有光线追踪器都具备的一个共同点,就是拥有一个 ray(射线)类,以及一个计算沿着射线能看到什么颜色的算法。我们可以把射线看作一个函数:$P(t)=a+tb$ 。在这里,$P$ 是三维空间中沿着一条直线的三维位置(坐标)。$A$ 是射线起点(ray origin),$B$ 是射线方向(ray direction)。射线参数 t 是一个实数(在代码中为 double 类型)。代入不同的 t 值,P(t) 就会沿着射线移动该点。如果加上负的 t 值,你可以到达这条三维直线上的任意位置。而对于正的 t 值,你只能得到 $A$ 前方的部分,这也就是人们常说的射线(half-line,或直接叫 ray)。
📄️ 5. 加入一个球体
让我们加入一个物件到光线追踪器。人们很常在光线追踪器中使用球体,因为计算一道光线是否击中一个球体相对简单。
📄️ 6. 表面法向量与多物体
首先,让我们获取一个表面法向量以进行着色。法向量是垂直于相交点表面的向量。
📄️ 7. 将相机代码移至它自己的类
在继续之前,现在是将我们的相机和场景渲染代码整合到一个单一新类的好时机:即 camera 类。camera 类将负责两项重要工作:
📄️ 8. 抗锯齿 (Antialiasing)
若你放大目前渲染出来的图像,可能会注意到边缘呈现出很明显的 “阶梯状” 粗糙感。这种阶梯状通常称为 “走样”(aliasing),或 “锯齿”(jaggies)。真实相机拍照时,边缘通常不会有锯齿,因为边缘处的像素往往是前景与背景的混合。注意到,与我们渲染出来的图像不同,真实世界中的图像是连续的。即,世界本身(以及它的任何真实图像)实际上都有无限分辨率。我们可以藉由对每个像素取一组样本并求平均,来取得类似的效果。
📄️ 9. 漫反射材质
现在我们已经有了物体,以及每个像素发射多条射线,因此可以开始制作一些看起来更真实的材质。我们先从漫反射材质 (Diffuse Materials,也称雾面材质 ) 开始。其中一个设计上的问题是:我们是否要将几何与材质分离,使一个材质可以指定给多个球体,反之亦然;还是要让几何与材质紧密绑定在一起。后者对于程序化生成的物体特别有用,因为它们的几何形状与材质通常是一起产生、彼此关联的。本文将采用分离式设计,也就是将几何与材质分开管理。这是大多数渲染器常见的做法,不过也请注意,实际上还存在其他不同的设计方式。
📄️ 10. 金属
如果我们希望不同的对象拥有不同的材质,那么就需要做一个设计决策。我们可以设计一种通用的材质类型,里面带有大量参数;这样每一种具体材质都可以直接忽略那些不会影响它的参数。这种做法并不差。或者,我们也可以设计一个抽象的材质类,用它来封装不同材质各自独特的行为。我个人更喜欢后一种做法。对于我们的程序来说,材质需要做两件事:
📄️ 11. 介电质
像水、玻璃和钻石这样的透明材料都是介电质。当一条光线打到它们时,光线会分成一条反射光线和一条折射(透射)光线。我们会通过在反射和折射之间随机选择来处理这件事,也就是每次相互作用只生成一条散射光线。
📄️ 12. 可定位相机
相机和电介质一样,都很难调试,所以我总是用循序渐进的方式来开发相机。首先,让我们允许视场角(fov)可调。视场角是渲染图像从一侧边缘到另一侧边缘所对应的视觉角度。由于我们的图像不是正方形,水平视场角和垂直视场角并不相同,我总是用垂直视场角。我也习惯用角度来指定它,并在构造函数内部将其转换为弧度——只是个人偏好。
📄️ 13. 散焦模糊
现在来介绍我们的最后一个功能:散焦模糊(defocus blur)。需要注意的是,摄影师通常把这种现象称为景深,所以在和你学光线追踪的朋友交流时,记得只使用 “散焦模糊” 这个说法。
📄️ 14. 接下来做什么?
让我们来生成这本书封面上的图像——由大量随机球体组成的场景。
📄️ 15. 致谢
原始手稿协助
📄️ 16. 引用本书
采用一致的引用格式,可以更容易地确定这部作品的来源、位置和版本。如果你需要引用本书,请尽可能使用以下格式之一。