1056 字
5 分钟
光线追踪:四边形图元

定义四边形#

quad目前代表一个平行四边形.想要定义一个平行四边形需要知道以下三点.

  1. Q,起始点
  2. u,表示第一条边向量
  3. v,表示第二条边向量

四个角为:

  1. Q
  2. Q + u
  3. Q + v
  4. Q + u + v

四边形是平面图形,因此如果四边形位于 XY、YZ 或 ZX 平面上,其轴对齐包围盒在某一维度上的厚度将为零。这可能导致光线相交计算时出现数值问题,但我们可以通过填充包围盒中尺寸为零的维度来解决

[aabb.h] 新增 aabb::pad_to_minimums() 方法:

private:
void pad_to_minimums() {
// Adjust the AABB so that no side is narrower than some delta, padding if necessary.
double delta = 0.0001;
if (x.size() < delta) x = x.expand(delta);
if (y.size() < delta) y = y.expand(delta);
if (z.size() < delta) z = z.expand(delta);
}

quad类草稿

#ifndef QUAD_H
#define QUAD_H
#include "hittable.h"
class quad : public hittable {
public:
quad(const point3& Q, const vec3& u, const vec3& v, std::shared_ptr<material> mat)
: Q(Q), u(u), v(v), mat(mat)
{
set_bounding_box();
}
virtual void set_bounding_box() {
// 通过两条对角线的包围盒合并出四个顶点的整体包围盒。
auto bbox_diagonal1 = aabb(Q, Q + u + v);
auto bbox_diagonal2 = aabb(Q + u, Q + v);
bbox = aabb(bbox_diagonal1, bbox_diagonal2);
}
aabb bounding_box() const override { return bbox; }
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
return false; // 待实现
}
private:
point3 Q;
vec3 u, v;
std::shared_ptr<material> mat;
aabb bbox;
};
#endif

光线与平面的交点#

平面可表示为方程: Ax+By+Cz=DAx + By + Cz = D 可以理解为法向量n=(A,B,C)n = (A,B,C)与位置向量v=(x,y,z)v = (x,y,z)
两向量的点积为D.

n(P+td)=D\mathbf{n} \cdot (\mathbf{P} + t\mathbf{d}) = D

求解 tt

nP+ntd=D\mathbf{n} \cdot \mathbf{P} + \mathbf{n} \cdot t\mathbf{d} = DnP+t(nd)=D\mathbf{n} \cdot \mathbf{P} + t(\mathbf{n} \cdot \mathbf{d}) = Dt=DnPndt = \frac{D - \mathbf{n} \cdot \mathbf{P}}{\mathbf{n} \cdot \mathbf{d}}

求出t之后可以带入射线方程得到交点.

寻找包含给定四边形的平面#

法向量n=(A,B,C)n = (A,B,C)可以用u、v向量的叉乘来得到 常量D:

D &= n_x Q_x + n_y Q_y + n_z Q_z \\ &= \mathbf{n} \cdot \mathbf{Q} \\ \end{align*}$$ 我们将使用 `normal` 和 `D` 这两个值来求给定光线与包含四边形的平面之间的交点 我们先实现 `hit()` 方法来处理包含四边形的无限平面 ```cpp bool hit(const ray& r, interval ray_t, hit_record& rec) const override { auto denom = dot(normal, r.direction()); // 如果光线与平面平行,则没有交点。 if (std::fabs(denom) < 1e-8) return false; // 如果交点参数 t 不在光线的有效区间内,则不算命中。 auto t = (D - dot(normal, r.origin())) / denom; if (!ray_t.contains(t)) return false; auto intersection = r.at(t); rec.t = t; rec.p = intersection; rec.mat = mat; rec.set_face_normal(r, normal); return true; } ``` ## 平面上的点定位 在平面上定位一个点需要坐标系,我们可以根据Q,u,v来表示交点P,也就是说P可以写成: $$\mathbf{P} = \mathbf{Q} + \alpha \mathbf{u} + \beta \mathbf{v}$$ 这里直接给出结果平面坐标 $\alpha$ 和 $\beta$ 由以下方程给出:

\alpha = \mathbf{w} \cdot (\mathbf{p} \times \mathbf{v})

\beta = \mathbf{w} \cdot (\mathbf{u} \times \mathbf{p})

其中 其中

\mathbf{p} = \mathbf{P} - \mathbf{Q}

\mathbf{w} = \frac{\mathbf{n}}{\mathbf{n} \cdot (\mathbf{u} \times \mathbf{v})} = \frac{\mathbf{n}}{\mathbf{n} \cdot \mathbf{n}}

对于给定的四边形,向量 $\mathbf{w}$ 是常量,因此我们会缓存该值。 ```cpp w = n / dot(n,n); vec3 w; ``` `[quad.h] 缓存四边形的 w 值` ## 利用UV坐标进行交点内部测试 要判断平面坐标为 (𝛼,𝛽) 的点是否位于四边形内部,只需满足以下条件 1. 0≤𝛼≤1 2. 0≤𝛽≤1 判断是否在区间里 ```cpp virtual bool is_interior(double a, double b, hit_record& rec) const { interval unit_interval = interval(0,1); if(!unit_interval.contains(a) || !unit_interval.contains(b)){ return false; } rec.u = a; rec.v = b; return true; } ``` 计算出alpha和beta ```cpp // 使用平面坐标判断交点是否落在四边形内部。 auto intersection = r.at(t); vec3 planar_hitpt_vector = intersection - Q; auto alpha = dot(w, cross(planar_hitpt_vector, v)); auto beta = dot(w, cross(u, planar_hitpt_vector)); if (!is_interior(alpha, beta, rec)) return false; ``` ## 额外的二维图元 在这里暂停一下,想想看:如果你用 (𝛼,𝛽) 坐标来判断一个点是否在四边形(平行四边形)内,那么不难想象,我们可以用同样的二维坐标来判断交点是否落在任何其他二维(平面)图元内部! 举个例子,假设我们修改 `is_interior()` 函数,使其在 `sqrt(a*a + b*b) < r` 时返回真。这样就能实现半径为 `r` 的圆盘图元。对于三角形,可以试试 `a > 0 && b > 0 && a + b < 1`
光线追踪:四边形图元
https://dingfengbo.vercel.app/posts/rtiow/四边形图元/
作者
Eureka
发布于
2026-05-28
许可协议
CC BY-NC-SA 4.0