3.6 光线(Rays)
光线(ray) \( \text{r} \) 是由其起点 \( \text{o} \) 和方向 \( \mathbf{d} \) 指定的半无限线(semi-infinite line);见图 3.8。 pbrt 使用 Point3f 作为起点和 Vector3f 作为方向来表示 Ray;在 pbrt 中不需要非 Float 类型的光线。有关 Ray 类的实现,请参见 pbrt 源代码分支中的 ray.h 和 ray.cpp 。
![]()
图 3.8: 光线是由其起点 \( \text{o} \) 和方向向量 \( \mathbf{d} \) 定义的半无限线。
/** Ray 定义 */
class Ray {
public:
/** Ray 公有方法 */
/** Ray 公有成员 */
};
因为我们将在代码中频繁引用这些变量, Ray 的原点和方向成员被简洁地命名为 o 和 d 。请注意,我们再次出于方便将数据公开可用。
/** Ray 公有成员 */
Point3f o;
Vector3f d;
光线的 参数形式(parametric form) 将其表示为标量值 \( t \) 的函数,给出了光线经过的点集:
\[ \text{r}(t) = \text{o} + t\mathbf{d} \quad 0 \leq t < \infty \]
Ray 类重载了光线的函数应用运算符,以便与方程 (3.4) 中的 \( \text{r}(t) \) 符号相匹配。
/** Ray 公有方法 */
Point3f operator()(Float t) const { return o + d * t; }
用这个方法,当我们需要找到沿着光线特定位置的点时,可以编写如下代码:
Ray r(Point3f(0, 0, 0), Vector3f(1, 2, 3));
Point3f p = r(1.7);
每条光线也都有一个与之相关的时间值。在有动画物体的场景中,渲染系统会在每条光线的适当时间构建场景的表示。
/** Ray 公有成员 */
Float time = 0;
每条光线还记录了其原点处的介质。将在第 11.4 节中介绍的 Medium 类封装了参与介质的属性(可能存在空间变化),例如有雾的大气、烟雾或像牛奶这样的散射液体。将这些信息与光线关联,使系统的其他部分能够正确处理光线从一种介质传递到另一种介质的效果。
/** Ray 公有成员 */
Medium medium = nullptr;
构造 Ray 是十分简单。默认构造函数依赖于 Point3f 和 Vector3f 的构造函数来将原点和方向设置为 \( (0,0,0) \) 。另外,可以提供特定的点和方向。如果提供了原点和方向,构造函数允许为光线的时间和介质给出值。
/** Ray 公有方法 */
Ray(Point3f o, Vector3f d, Float time = 0.f, Medium medium = nullptr)
: o(o), d(d), time(time), medium(medium) {}
3.6.1 光线微分(Ray Differentials)
为了能够使第 10 章中定义的纹理函数进行更好的抗锯齿, pbrt 需利用 Ray 的子类 RayDifferential 类,它包含两条辅助光线的额外信息。这些额外的光线代表胶片平面上的主光线在 \( x \) 和 \( y \) 方向上偏移一个样本的相机光线。通过确定这三条光线在被着色的物体上投影的区域, Texture 对象可以估算一个进行适当的抗锯齿处理的平均区域(第 10.1 节)。
因为 RayDifferential 继承自 Ray ,系统中的几何接口可以编写为接受 const Ray & 参数,以便可以向它们传递 Ray 或 RayDifferential 。只有需要考虑抗锯齿和纹理的例程才需要 RayDifferential 参数。
/** RayDifferential 定义 */
class RayDifferential : public Ray {
public:
/** RayDifferential 公有方法 */
/** RayDifferential 公有成员 */
};
RayDifferential 构造函数与 Ray 的构造函数相似。
/** RayDifferential 公有方法 */
RayDifferential(Point3f o, Vector3f d, Float time = 0.f,
Medium medium = nullptr)
: Ray(o, d, time, medium) {}
在某些情况下,微分光线可能不可用。接受 RayDifferential 参数的例程在访问微分光线的起点或方向之前,应检查 hasDifferentials 成员变量。
/** RayDifferential 公有成员 */
bool hasDifferentials = false;
Point3f rxOrigin, ryOrigin;
Vector3f rxDirection, ryDirection;
还有一个构造函数可以从 Ray 创建一个 RayDifferential 。参照前面的构造函数, hasDifferentials 成员变量的默认为 false 保持不变。
/** RayDifferential 公有方法 */
explicit RayDifferential(const Ray &ray) : Ray(ray) {}
pbrt 中 Camera 的实现是在假设摄像机光线间隔一个像素的情况下,计算离开摄像机的光线的微分。积分器通常为每个像素生成多条摄像机光线,在这种情况下,样本之间的实际距离较小,微分也应相应更新;如果不考虑这一因素,图像中的纹理通常会显得过于模糊。下面的 ScaleDifferentials() 方法处理此问题,需传入估计的样本间距 s 。例如,它在第 1 章的代码片段 << 为当前样本生成摄像机光线 >> 中被调用。
/** RayDifferential 公有方法 */
void ScaleDifferentials(Float s) {
rxOrigin = o + (rxOrigin - o) * s;
ryOrigin = o + (ryOrigin - o) * s;
rxDirection = d + (rxDirection - d) * s;
ryDirection = d + (ryDirection - d) * s;
}