1056 字
5 分钟
光线追踪:四边形图元
定义四边形
quad目前代表一个平行四边形.想要定义一个平行四边形需要知道以下三点.
- Q,起始点
- u,表示第一条边向量
- v,表示第二条边向量

四个角为:
- Q
- Q + u
- Q + v
- 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光线与平面的交点
平面可表示为方程:
可以理解为法向量与位置向量
两向量的点积为D.
求解 :
求出t之后可以带入射线方程得到交点.
寻找包含给定四边形的平面
法向量可以用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`