Ray Tracing⚓︎
约 1762 个字 预计阅读时间 9 分钟
为什么要学习光线追踪(ray tracing)?因为:
-
光栅化不能处理全局上的效果,包括
- 软阴影
- 尤其是光线反复弹射(>1)的场景
-
光栅化很快,但是质量相对较低
-
光线追踪很准确,但是很慢
- 我们认为光栅化是实时的(real-time),而光线追踪是离线的(offline)
- 生产中过程中,10000 个 CPU 核心小时才能渲染一帧画面
Whitted-Style Ray Tracing⚓︎
Basic Ray-Tracing Algorithm⚓︎
在光线追踪算法中,我们对光线有以下假定:
- 光沿直线传播(尽管这是错的,以为光有波动性)
- 多条光线相交时不会发生“碰撞”(尽管这也是错的)
-
光线从光源到达人眼
- 光线从光源到人眼,那么从人眼出发也能看到光线,这就是光路的可逆性(reciprocity)
“And if you gaze long into an abyss, the abyss also gazes into you.” — Friedrich Wilhelm Nietzsche(尼采)
人们对光线追踪的研究可以追溯至几千年前。一开始,不少人认为因为人眼向外界散播一种“感受光线”的东西,我们才能看到身边的世界。现在看来这种理论是十分荒谬的。

而在图形学的光线追踪算法中,首先要了解光线投射(ray casting) 的原理:
- 通过为每个像素点投射一束光线来生成一幅图像
- 通过将光线发射到光源来检查阴影的存在

这里的“光线”来自人眼,并且之后我们就把人眼看作是一个针孔相机 (pinhole camera)。下面展示了从人眼出发的光线照到球面,并从球面出发又经过了很多物体的情景。

虽然这一束光线和场景中多个物体相交,但我们只考虑离人眼最近的那个交点。对于该交点,执行着色计算,得出该光线对应像素的颜色值。

这种基于光线投射的光线追踪算法叫做递归光线追踪(recursive ray casting),或 Whitted 风格光线追踪,是一种“改进的阴影显示照明模型”。下图就是采用该算法得到的结果:

在不同硬件上的耗时对比:
- VAX 11/780 (1979):74m
- PC (2006):6s
- GPU (2012):1/30s
还是利用前面介绍的例子,现在我们仅考虑照到最近交点的那一段光线。由这条光线,产生其他类型的光线:
-
反射光线(reflected ray)(镜面反射 (specular reflection))
-
折射光线(refracted ray)(镜面透射 (specular transmission))
-
阴影光线(shadow ray)
我们称入射光线为主光线(primary ray),而反射光和折射光线被称为次级光线(secondary ray)。
Ray-Surface Intersection⚓︎
光线可被简单表示为一个原点 + 方向向量(单位向量,长度为 1
- \(\mathbf{r}(t)\):沿着光线上的点
- \(t\):时间
- \(\mathbf{o}\):原点
- \(\mathbf{d}\)
: (归一化后的)方向向量
Spheres⚓︎
先来看如何求光线在球面上的交点:已知
- 光线:\(\mathbf{r}(t) = \mathbf{o} + t\mathbf{d} \quad 0 \le t < \infty\)
- 球体:\(\mathbf{p}:\ (\mathbf{p} - \mathbf{c})^2 - R^2 = 0\)
那么交点必然同时满足上述两个方程,所以只要将光线方程代入到球体方程即能求解。 $$ (\mathbf{o} + t\mathbf{d} - \mathbf{c})^2 - R^2 = 0 $$
因为这是一个二次方程,所以可以写成 \(at^2 + bt + c = 0\) 的形式,其中
- \(a = \mathbf{b} \cdot \mathbf{b}\)
- \(b = 2(\mathbf{o} - \mathbf{c}) \cdot \mathbf{d}\)
- \(c = (\mathbf{o} - \mathbf{c}) \cdot \mathbf{o} - \mathbf{c} - R^2\)
求根公式 \(t = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a}\),将 \(a, b, c\) 代入就能得到最终结果。
注
圆和直线的关系包括相离、相切和相交。

Implicit Surfaces⚓︎
更一般地,考虑光线和用隐式法表示的曲面的相交。假设曲面方程为 \(\mathbf{p}:\ f(\mathbf{p}) = 0\),将光线方程代入后求解,其中的正实根就是最终解。

Planes⚓︎
而对于用显式法表示的曲面,三角形是其中最基础,也是最重要的一个。之所以要研究光线和三角形网格的相交关系,是因为
- 从渲染角度看,可见性、阴影和光照等都会涉及到
- 从几何角度看,检测点在几何体的内外
- 检验方法:从该点出发打出一条射线,如果射线经过奇数个点,说明该点在几何体内部,否则在外面
最简单的思路是让光线穿过每一个能够穿过的三角形面。简单起见,我们认为一条光线和一个三角形的相交次数为 0 或 1(忽略多次相交的可能
由于三角形是一个平面,因此可以将问题转化为求光线和平面(planes) 的相交,并检验交点是否落在三角形内部。平面由它的法向量以及一个平面上的点来定义,对应的方程为: $$ \mathbf{p}: (\mathbf{p} - \mathbf{p}') \cdot \mathbf{N} = 0 $$
- \(\mathbf{p}\):平面上的所有点
- \(\mathbf{p}'\):平面上一点
- \(\mathbf{N}\):法向量
注:平面方程的一般式:\(ax + by + cz + d = 0\)
同样可以将光线方程代入(令 \(\mathbf{p} = \mathbf{r}(t)\)
这样计算可能还是太麻烦了,一种更快的做法叫做 Möller Trumbore 算法。它利用重心坐标计算,方程和解如下所示:

其中 \((1 - b_1 - b_2), b_1, b_2\) 都是重心坐标。
Accelerating Ray-Surface Intersection⚓︎
光线追踪技术对计算机性能提出了不小的挑战。就以前面介绍的简单的光线 - 场景相交算法为例,我们需要测试每一个三角形和每一条光线的相交情况,并找出其中最近的交点(即 \(t\) 最小时对应的点
例子

圣米格尔:该场景包含 10.7M 个三角形

植物生态系统:该场景包含 20M 个三角形(植物的叶子很多且复杂)
注意
为求通用性,我们后续使用“对象”一词替代“三角形”(但未必指整个对象
Bounding Volumes⚓︎
为避免计算光线与复杂物体上的相交关系,我们可以用一个结构简单的包围体(bounding volume) 覆盖复杂物体的周围。注意包围体内的物体一定要尽可能填满整个空间。如果光线没有经过包围体,也就意味着没有经过包围体内的物体,因此检测时可以先看光线是否经过包围体,再看是否经过包围盒内的物体。

现在我们用一个盒子作为包围体,这个盒子与三对面 (slabs)(也就是长方体的六个面)相交(右图展示了其中一对面
为方便讨论,下面以二维平面上的 AABB 为例讲解具体的计算过程,三维空间同理。核心思想是计算光线到达每一对面的最小时间和最大时间(\(t_{\min}, t_{\max}\),可以是负数

上述计算是合理的原因是:
- 仅当光线进入所有对的面,光线才算进入到包围盒
- 只要光线离开其中一对面,光线就算离开了包围盒
对应的公式为:\(t_{\text{enter}} = \max \{t_{\min}\}, t_{\text{exit}} = \min \{t_{\max}\}\)。当 \(t_{\text{enter}} < t_{\text{exit}}\),我们认为光线在包围盒内经过一会儿,所以它们必定会相交。然而光线不是直线,所以还需检查 \(t\) 是否为正,否则这样的解是无效的。
- \(t_{\text{exit}} < 0\):说明盒子在光线的“后面”,因此无法相交
- \(t_{\text{exit}} \ge 0, t_{\text{enter}} < 0\):光线的原点在盒子内,所以必定相交
所以当且仅当 \(t_{\text{enter}} < t_{\text{exit}} \&\& t_{\text{exit}} \ge 0\) 时,光线和 AABB 相交。
之所以要让包围盒轴对齐,是因为可以简化光线到平面上的计算。

评论区