跳转至

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) 的原理:

  1. 通过为每个像素点投射一束光线来生成一幅图像
  2. 通过将光线发射到光源来检查阴影的存在

这里的“光线”来自人眼,并且之后我们就把人眼看作是一个针孔相机 (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) = \mathbf{o} + t\mathbf{d} \quad 0 \le t < \infty $$

  • \(\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)\),解得 \(t = \dfrac{(\mathbf{p}' - \mathbf{o}) \cdot \mathbf{N}}{\mathbf{d} \cdot \mathbf{N}}\)。当 \(0 \le t < \infty\) 时解才有效。

这样计算可能还是太麻烦了,一种更快的做法叫做 Möller Trumbore 算法。它利用重心坐标计算,方程和解如下所示:

其中 \((1 - b_1 - b_2), b_1, b_2\) 都是重心坐标。

Accelerating Ray-Surface Intersection⚓︎

光线追踪技术对计算机性能提出了不小的挑战。就以前面介绍的简单的光线 - 场景相交算法为例,我们需要测试每一个三角形和每一条光线的相交情况,并找出其中最近的交点(即 \(t\) 最小时对应的点。所以运行时间 = 像素个数(光线条数) x 三角形个数(x 弹射次数,耗时很长。

例子

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

植物生态系统:该场景包含 20M 个三角形(植物的叶子很多且复杂)

注意

为求通用性,我们后续使用“对象”一词替代“三角形”(但未必指整个对象

Bounding Volumes⚓︎

为避免计算光线与复杂物体上的相交关系,我们可以用一个结构简单的包围体(bounding volume) 覆盖复杂物体的周围。注意包围体内的物体一定要尽可能填满整个空间。如果光线没有经过包围体,也就意味着没有经过包围体内的物体,因此检测时可以先看光线是否经过包围体,再看是否经过包围盒内的物体。

现在我们用一个盒子作为包围体,这个盒子与三对面 (slabs)(也就是长方体的六个面)相交(右图展示了其中一对面。因而称这样的包围体为轴对齐包围盒(axis-aligned bounding box, AABB),即包围盒的任意边是沿着 x, y z 轴方向的。

为方便讨论,下面以二维平面上的 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 相交。

之所以要让包围盒轴对齐,是因为可以简化光线到平面上的计算。

评论区

如果大家有什么问题或想法,欢迎在下方留言~