基本假设
经典 光线追踪 Ray Tracing 从几何光学出发,采用三个近似:
- 光在均匀介质中沿直线传播;
- 光线相遇时互不影响;
- 光路可逆,从相机反向追踪到光源与从光源正向传播等价。
这些假设忽略了衍射、干涉等波动现象,却足以解释遮挡、镜面反射和折射。反向追踪只计算最终到达相机的路径,可以避免从光源发出大量永远不会落到传感器上的光线。
相机光线
设针孔相机中心为 e,成像平面上某个像素的采样点为 s,对应的主光线写成
r(t)=e+td,d=∥s−e∥s−e,t≥0.
Ray Casting 为每个像素发射一条主光线,取最近交点并计算直接光照。Whitted-Style Ray Tracing 还会在交点继续生成反射光线、折射光线与指向光源的阴影光线,通过递归组合得到玻璃、镜面和硬阴影。


递归必须设置终止条件,例如最大深度或路径贡献阈值。每次求交都要忽略出发点附近很小的一段区间,或者沿法线偏移新光线的起点,否则浮点误差会让光线立刻再次击中原表面。
光栅化从三角形出发,寻找它覆盖的像素,并用 Z-buffer 在同一像素的多个片元中选择最近者;光线追踪从像素出发,在整条射线上选择最小有效 t。两者都要解决最近可见面,组织查询的方向不同。光线追踪的主光线可见性不需要另建屏幕 Z-buffer,但每条射线的求交器仍在维护“当前最近 t”。材质着色模型并不专属于某一种方法。
隐式求交
对隐式曲面 f(p)=0,把光线方程代入即可得到关于 t 的一元方程:
f(o+td)=0.
球心为 c、半径为 R 的球满足
∥p−c∥2=R2.
令 q=o−c。代入光线并展开:
∥o+td−c∥2=(q+td)⋅(q+td)=(d⋅d)t2+2(d⋅q)t+q⋅q.
与 R2 移到同一侧,得到
At2+Bt+C=0,
其中
A=d⋅d,B=2d⋅q,C=q⋅q−R2.
判别式与两个根为
Δ=B2−4AC,t±=2A−B±Δ.
Δ<0 表示没有实交点,Δ=0 表示相切,Δ>0 时光线穿过球面两次。渲染器不会简单取“最小非负根”,而是在当前射线区间 [tmin,tmax] 内选最小根。主光线通常令 tmax=+∞;指向点光源的阴影光线必须把 tmax 限制在光源距离之前,避免把光源后方的物体误判为遮挡。若方向已归一化,则 A=1。

平面求交
给定平面上一点 p′ 和单位法线 n,平面方程为
(p−p′)⋅n=0.
代入光线可得
t=d⋅n(p′−o)⋅n.
当 d⋅n=0 时,光线与平面平行。若分子也为零,整条光线位于平面内,交点不唯一;否则没有交点。非平行情形仍要检查 tmin≤t≤tmax。有限多边形还需在得到平面交点后做边界测试。
三角形求交
三角形网格是最常见的显式几何。直接做法是先求光线与三角形所在平面的交点,再用叉积或重心坐标判断交点是否位于三角形内部。
Möller–Trumbore 求交算法 把两个步骤合并。令
E1=P1−P0,E2=P2−P0,S=O−P0,
求解
O+tD=P0+b1E1+b2E2.
移项后得到一个 3×3 线性系统:
(−DE1E2)tb1b2=S.
系数矩阵的行列式是
Δ=det[−D,E1,E2]=(D×E2)⋅E1.
Δ=0 表示三个列向量线性相关,即光线与三角形平面平行或三角形退化。定义
S1=D×E2,S2=S×E1.
用 Cramer 法则把对应列替换为 S,再把行列式写成标量三重积:
b1=ΔS1⋅S,b2=ΔS2⋅D,t=ΔS2⋅E2.
三角形上的点为
P=(1−b1−b2)P0+b1P1+b2P2,
所以三个重心权重为 (1−b1−b2,b1,b2)。有效交点满足
tmin≤t≤tmax,b1≥0,b2≥0,b1+b2≤1.
这些权重还能插值法线、纹理坐标和其他顶点属性。若启用背面剔除,可以利用 Δ 的符号排除背向三角形;双面材质只判断 ∣Δ∣ 是否足够大。
Möller–Trumbore 同时解出了光线参数和三角形重心坐标,因此不需要先求平面交点再做一次内部测试。
求交代价
若场景含有 N 个三角形,每条光线逐一测试的成本为 O(N)。一个像素可能生成多条次级光线,路径追踪还会对每个像素采样很多次,因此朴素算法很快成为瓶颈。
加速结构利用一个事实:多数光线只经过场景中的很小区域。先用便宜的包围体排除大片不相关几何,再对少量候选图元做精确求交。在树较平衡、包围盒重叠较少且几何分布合理时,期望访问量常接近对数级;它不是对任意场景和任意光线的最坏情况保证。
包围盒
轴对齐包围盒 Axis-Aligned Bounding Box,简称 AABB,是三个轴向区间的笛卡尔积:
[x0,x1]×[y0,y1]×[z0,z1].
沿每个轴,它对应一对平行平面。光线在第 i 个轴上进入和离开的参数为
ti,0=dipi,0−oi,ti,1=dipi,1−oi.
不必单独依据方向交换两个值,直接定义
ti,min=min(ti,0,ti,1),ti,max=max(ti,0,ti,1).
第 i 个平板 Slab 允许的射线参数区间是 [ti,min,ti,max]。射线位于盒内,要求三个轴的条件同时成立,因此有效区间是集合交:
[tenter,texit]=i∈{x,y,z}⋂[ti,min,ti,max].
区间交的左端点取所有左端点最大值,右端点取所有右端点最小值:
tenter=imaxti,min,texit=iminti,max.
最终还要与射线自身区间 [tmin,tmax] 求交。命中条件为
max(tenter,tmin)≤min(texit,tmax).
若 di=0,光线在该轴上没有移动:原点不在对应 slab 内则立即不相交,原点在其中则该轴不限制参数区间。
当 tenter=texit 时,光线只接触盒子的面、棱或角。若交点边界被视为有效,应使用 tenter≤texit;只有在算法明确要求穿过正体积时才使用严格不等式。实现中还要先把该区间与 [tmin,tmax] 相交,否则盒子虽然命中,交点可能仍位于射线的反向或已裁剪部分。
均匀网格
均匀网格 Uniform Grid 先建立场景包围盒,再把空间划成规则单元:
- 将每个物体登记到与其包围盒重叠的单元;
- 光线从进入场景的位置开始,按穿越顺序访问网格;
- 只测试当前单元中的物体;
- 找到比下一个单元边界更近的交点后即可停止。
网格分辨率太低时,一个单元包含过多几何;太高时,存储和穿越成本上升。规则网格适合几何分布较均匀的场景。若大量三角形集中在小茶壶里,而周围是广阔空旷空间,固定分辨率很难同时照顾两种尺度,这类情形常称为“teapot in a stadium”。

空间划分
层次空间划分根据局部复杂度继续细分。Octree 每次把三维盒子分成八块,结构简单但不能顺应几何方向。BSP Tree 可以使用任意切分平面,表达能力强,构建和求交也更复杂。KD-Tree 每层选择一个坐标轴并用轴对齐平面二分空间,三种轴通常轮换或按范围选择。
KD-tree 的内部节点保存切分轴和位置,叶节点保存与该空间重叠的图元。遍历时先访问光线进入的一侧;只有另一侧可能包含更近交点时才继续。一个横跨切分面的物体可能被同时登记到两个子空间,构建时必须权衡节点开销、重复图元和叶节点求交成本。
BVH
包围盒层次结构 Bounding Volume Hierarchy,简称 BVH,按物体集合划分,而不是切割空间:
- 为当前图元集合计算包围盒;
- 选择包围盒最长的轴;
- 按图元中心在该轴上的位置排序,并在中位数附近二分;
- 递归建立两个子节点,直到叶节点只含少量图元。
中位数划分能保持树高近似平衡。更高质量的实现常用 表面积启发式 Surface Area Heuristic 估计不同切分的遍历成本。叶节点容纳多少图元取决于节点开销和三角形求交代价,课程示例中可取约五个作为起点。
遍历时先测试当前节点的包围盒。未命中便立即返回;叶节点逐个测试图元;内部节点优先访问较近的子盒并保留较近结果。找到当前最近交点 thit 后,令
tmax←thit,
后续包围盒与新的射线区间没有交集时便能立即剪枝。
实现递归遍历时,近子节点和远子节点应各传入一次;排序后又执行一次无条件的公共递归,可能重复访问同一子节点并抵消剪枝收益。划分轴也必须针对当前子盒重新计算最长轴,不能把根节点的轴一路复用到所有后代。每次包围盒测试都要使用该盒自己的 tenter、texit,并接受相切边界 tenter≤texit。
交点偏移的 ε 不应写成脱离场景的固定大常数,通常取
ε=max(εabs,εrels),
其中 s 可以是命中物体、当前 BVH 盒或场景包围盒的特征尺度。ε 过大可能跳过很薄的几何或漏掉近距离交点,过小则无法压制自相交和浮点抖动;反射、折射、阴影光线还应结合法线方向和射线区间分别设置偏移。

KD-tree 的叶节点对应互不重叠的空间区域,但同一图元可能重复出现;BVH 的子包围盒可以相互重叠,每个图元通常只属于一个叶节点。现代离线和实时光线追踪更常采用 BVH,因为它容易动态更新,也适合三角形场景与硬件遍历。