Ray Tracing - Intersection

在光线追踪算法中, 光线与各类几何体求交, 是渲染着色的基础. 几何体根据表示方式不同可以分为两个大类:

  • 隐式曲面, Implicit Surface. (隐含几何体的点信息)
  • 显式曲面, Explicit Surface. (直接包含几何体的点线面信息)

几何体的隐式曲面和显式曲面表示形式, 区别有点类似于图形与图像的区别.

  • 图形: 具有数学表达的几何对象 (点线面); 例如, 矢量图.
  • 图像: 离散表达, 像素, 光栅化; 例如, 栅格图.

关于隐式曲面和显示曲面的详细介绍可以参考 [1]

Ray Equation

Ray, 光线, 定义为射线, 从一个点出发, 沿着某一方向前行.

Ray Tracing - Intersection

在 Ray 传播路径上的点 \(P(x_p,y_p,z_p)\), 都可以表示为以下形式:

\[(x_p, y_p, z_p) = (o_x+td_x, o_y+td_y, o_z+td_z) = \vec{o} + t\vec{d} \]

\(t\) 的直观含义: 从光源出发, 沿着光线传播方向传播的距离/时间.

Implicit Surface

隐式曲面不会告诉任何点的信息, 只会告诉该曲面上所有点满足的关系.

三维空间中的隐式曲面可以统一表示为以下形式:

\[f(x,y,z) = 0 \]

Ray 与曲面相交, 交点即在Ray上, 又在几何体上. 即:

\[f(x,y,z) = f(o_x+td_x, o_y+td_y, o_z+td_z) = 0 \]

只有一个变量 \(t\), 因此对于各种隐式曲面表达式, 结合各种数值方法 [2], 总能找到一个合适的解.

Sphere

交点计算结果

Ray Tracing - Intersection

具体推导过程

对于简单几何体, 例如球, 我们可以推导交点与法线的坐标表示.

Ray Tracing - Intersection

球的隐式曲面表示: 假设圆心为 \(\vec{c} = (c_x, c_y, c_z)\)

\[(x-c_x)^2 + (y-c_y)^2 + (z-c_z)^2 = R^2 \]

交点 \(P(x_p,y_p,z_p)\) 即在 Ray 上, 又在 Sphere 上, 满足:

\[\begin{cases} (x_p, y_p, z_p) = (o_x+td_x, o_y+td_y, o_z+td_z) \\ (x_p-c_x)^2 + (y_p-c_y)^2 + (z_p-c_z)^2 = R^2 \\ \end{cases} \]

此时未知量仅有 \(t\), 即:

\[At^2 + Bt + C = 0 \\ \]

\(A, B, C\) 的向量形式表示如下:

\[\begin{cases} A = \vec{d} \cdot \vec{d} \\ B = 2(\vec{o} - \vec{c}) \cdot \vec{d} \\ C = (\vec{o}-\vec{c}) \cdot (\vec{o}-\vec{c}) - R^2 \end{cases} \]

对于 \(t\) 为未知量的二元一次方程, 存在闭式解:

\[t=\frac{-B \pm \sqrt{B^{2}-4 A C}}{2 A} \]

根据 \(t\) 的含义可知, \(t\) 的最小正数解对应于光线与物体最近的点 \(P\), 正数解的个数对应于球与光线的交点个数.

  • 交点 \(P = \vec{o} + t\vec{d}\);
  • 法线 \(\vec{n} = \vec{CP}\), (圆心与交点的连线)

代码实现

auto x = ray.o - center;
double a = dot(ray.d, ray.d);
double b = 2 * dot(x, ray.d);
double c = dot(x, x) - radius * radius;

double t0, t1;
if (!Quadratic(a, b, c, &t0, &t1)) return false;
if (t0 > ray.tmax || t1 < 0.0) return false;

auto t = t0 < 0.0? t1: t0;
auto p_hit = ray.at(t);
auto p_norm = (p_hit - center).normalized();

Explicit Surface

Triangle Mesh, 三角网格, 由多个三角形组成, 是一类重要的隐式曲面. 三维物体常常组织为 Mesh 形式. 光线与三角形求交是光线与 Mesh 求交的基础.

Triangle Mesh

  • 在不使用加速结构的情况下, 可以测试 Ray 与 Mesh 中的每一个三角形检测是否相交, 所有交点中最近的交点位置, 即是 Ray 与 Mesh 的交点.
  • 当 Mesh 表示的物体有"内外"之分时: 奇数交点表示光源在 Mesh 内部; 偶数交点表示光源在物体外部. (不考虑相切)

Ray Tracing - Intersection

  • 简单实现: 遍历每个三角形, 获取 Ray 与 Mesh 的交点.
bool TriMesh::intersect(const Ray &ray, double &t_hit, Interaction &isect) const {
    bool hit = false;
    for (auto triangle : triangles) {
        if (triangle.get()->intersect(ray, t_hit, isect)) {
            ray.tmax = t_hit;
            hit = true;
        }
    }
    return hit;
}

Triangle

有多种方法可以计算射线与三角形的交点, 比如高效的 Möller–Trumbore 算法[3]. 这里我们使用最直观的一种方式, 将射线与三角形求交分解为一下两个过程:

  • 计算射线与三角形所在平面的交点
  • 判断交点是否在三角形内部

交点计算结果

计算射线与三角形所在平面的交点计算;

Ray Tracing - Intersection

判断交点是否在三角形内部 (叉乘判断) [4]

Ray Tracing - Intersection

  • 除了使用叉乘同向法, 还有内角和法[5], 面积法[6]等方法, 来计算判断一个点是否在三角形内部.
    • 面积法: 当点在三角形内部时, 有 \(S_{\triangle{ABC}} = S_{\triangle{OAB}}+S_{\triangle{OBC}}+S_{\triangle{OCA}}\).

Ray Tracing - Intersection

代码实现

// 1. Ray 与 三角形所在的平面相交, 得到交点 p
double t = dot((a - ray.o), n) / dot(ray.d, n);
if (t < 0 || t > ray.tmax) return false;
// 2. 判断交点 p 是否在三角形内部
auto p = ray.at(t);
auto ap = p - a;
auto bp = p - b;
auto cp = p - c;
auto t0 = cross(ab, ap);
auto t1 = cross(bc, bp);
auto t2 = cross(ca, cp);
auto ret_0 = dot(t0, t1);
auto ret_1 = dot(t1, t2);
auto ret_2 = dot(t2, t0);
// 3. 使用点乘判断叉乘结果是否同向
if (ret_0 < 0 || ret_1 < 0 || ret_2 < 0) return false;

References


  1. 计算机图形学九:隐式曲面(代数形式,CSG, 距离函数,分型几何)与显式曲面 ↩︎

  2. 数值计算非线性方程求根 ↩︎

  3. Möller–Trumbore intersection algorithm ↩︎

  4. Cross product ↩︎

  5. 判断点是否在三角形内 ↩︎

  6. 如何判断一个点是否在三角形内? ↩︎

上一篇:leetcode.929.UniqueEmailAddresses


下一篇:Ray Tracing in One Weekend01无法查看ppm的问题及一个C++字符缓冲传参引发的bug