光线追踪(一)——射线与摄像机
光线追踪的核心问题可以说得很朴素:屏幕上的一个像素,应该呈现什么颜色?光栅化从三角形出发,把几何投影到屏幕;光线追踪则从像素出发,向场景发射查询射线,寻找最先遇到的表面,再沿反射、折射或阴影方向继续查询。后者很自然地表达了遮挡、镜面与透明介质,也为基于物理的渲染提供了统一框架。
真实光子从光源出发,只有极少数最终进入摄像机。若正向模拟全部光子,大部分计算与画面无关。几何光学满足光路可逆,因此基础渲染器通常反向从摄像机追踪路径。这种算法更准确的名字是“路径追踪”;“光线追踪”在本文中作为广义称呼使用。
一、光线追踪基础
1. 渲染方程与蒙特卡洛估计
一个表面点 沿方向 离开的辐亮度满足渲染方程
其中 是自发光, 是双向反射分布函数, 是来自半球 的入射辐亮度, 是表面法向量。积分很少有闭式解,于是我们用蒙特卡洛(Monte Carlo)方法:随机选择方向 ,用样本均值近似:
其中 是采样概率密度。样本越多,噪声通常越小;但蒙特卡洛误差只按 收敛,把误差减半大约需要四倍样本。
2. 向量、颜色与射线
三维点和向量都可以用三个实数存储,但语义不同:点表示位置,向量表示位移。若 、,则
点积给出夹角关系 ;叉积得到同时垂直于两向量的方向。归一化 只改变长度,不改变方向。
颜色也可复用三分量向量,令 ,颜色分量约定在 。线性空间中的 并不等于显示器编码值的 。本系列所有的中间计算都在线性空间完成,只有写出文件前才做一次编码;否则材质的乘性衰减、路径能量与像素平均都会在错误尺度上进行。简单教程常用 gamma 2 近似,在输出前计算
给定起点 和方向 ,射线是参数曲线

图 图 1 也解释了为什么“射线”与“直线”不同:直线允许任意实数参数,而从 出发的射线只保留 的半支。改变 的长度会改变参数刻度,却不会改变射线经过的几何位置。
Rust 中只需保存两个向量,并实现沿射线取点:
#[derive(Clone, Copy, Debug)]
pub struct Ray {
pub origin: Point3,
pub direction: Vec3,
}
impl Ray {
pub fn at(self, t: f64) -> Point3 {
self.origin + t * self.direction
}
}二、射线与摄像机
1. 针孔摄像机
先看一个位于原点、朝 方向观察的摄像机。设视口高度为 、宽高比为 ,则视口宽度为 。若视口左下角为 ,水平边向量为 ,竖直边向量为 ,归一化像素坐标为 ,那么穿过视口采样点的射线为
为了任意摆放摄像机,给定观察位置 、目标点 与参考上方向 ,构造右手正交基
这里 是观察方向, 向右, 向上。垂直视场角为 、焦平面距离为 时,视口高度与宽度分别是

2. 像素采样与抗锯齿
若每个像素只在中心发射一条射线,物体边缘会出现阶梯。像素其实覆盖一个小面积,正确的像素值是该区域上辐亮度的积分。最简单的办法是在像素内加入均匀随机扰动:
let u = (x as f64 + rng.f64()) / (width - 1) as f64;
let v = (y as f64 + rng.f64()) / (height - 1) as f64;
color += ray_color(&camera.ray(u, v, &mut rng), world, depth, &mut rng);累加 个样本后一定要先除以 ,再做 gamma 编码。忽略这一步会让颜色迅速饱和:
let scale = 1.0 / samples as f64;
let encode = |c: f64| {
(256.0 * (scale * c).sqrt().clamp(0.0, 0.999)) as u8
};三、程序实现
1. 最小渲染循环
暂时没有物体时,可以用射线方向生成天空渐变。这一步用到单线性插值:给定两个端点值 与权重 ,插值点为

当 时得到 ,当 时得到 ,中间取值沿两者之间匀速变化。令单位射线方向的 分量映射为 ,再在白色与蓝色之间插值:

图 图 4 是配套工程 stages 二进制程序生成的实际结果,而不是装饰性封面。它同时验证了摄像机射线方向、线性插值和 PPM 写出流程;在加入任何物体之前,先得到这幅图有助于把几何错误与图像输出错误分开。
渲染器的控制流由此确定:逐行访问像素;在像素内生成多个摄像机样本;查询场景;累加线性颜色;平均、编码并写入图片。下一篇只需要把“查询场景”替换为球体求交,并让命中点根据材质生成下一条射线。
2. 实现检查清单
- 向量应支持点积、叉积、逐分量乘法、归一化与下标访问。
- 射线方向可以不归一化,但公式之间必须保持一致。
- 颜色累加发生在线性空间,平均之后再 gamma 编码。
- 相机上方向与观察方向不能平行。
- 随机数生成器应显式传递;固定种子便于复现实验和测试。
配套 Rust 工程中的 vec3.rs、ray.rs、camera.rs 与 main.rs 实现了本篇全部结构。第二篇将在此基础上加入几何求交、材质散射与薄透镜景深。
Illustrated by