🚀 快速射线盒求交:来自 Slab 宫殿的三次乘法魔法

134 阅读3分钟

“在三维空间的迷宫中,谁能判断光线会不会穿越那方方正正的盒子?答案藏在 Slab 的神殿中——只要三次乘法,你就能获得预言。”
—— 伽玛·冯·图形学,第十卷,浮点纪元。


🧭 引子:什么是“盒子求交”?

在计算机图形学的世界中,我们常需要判断:
“一条光线是否撞上了某个盒子?”

这在很多场景中都至关重要,比如:

  • 射线拾取(Ray Picking)
  • 碰撞检测
  • 光线追踪中的加速结构(BVH、KD-Tree)

而我们讨论的这个“盒子”,通常是 轴对齐包围盒(AABB, Axis-Aligned Bounding Box) ,就是说它永远不歪头,不斜眼,乖乖地沿着 XYZ 轴站着。


🎩 Slab 法的魔法:空间切片的哲学

Slab 这个词的本意是“厚板、层板”的意思,在这里,它指的是——

在每个坐标轴上由两个平面夹起来的一层空间区域

换句话说,一个 3D AABB,可以看成是 X、Y、Z 三个 Slab 的交集

⛓️ 举个例子:

假设你有一个盒子:

const boxMin = { x: -1, y: -1, z: -1 };
const boxMax = { x:  1, y:  1, z:  1 };

这表示这个盒子从 -1 到 1,在每个坐标轴上都占有一席之地。

一条光线:

const origin = { x: 0, y: 0, z: -5 }; // 从盒子前面出发
const direction = { x: 0, y: 0, z: 1 }; // 沿 Z 正方向射出

现在问题是:这条光线,会穿过这个盒子吗?Slab 法说:“只需三次乘法,我就告诉你。”


⚙️ Slab 法原理:拿时间去切割空间

我们先抽象下光线的定义:

光线 = 起点 + t × 方向
对于某个轴,比如 X 轴:
x(t) = origin.x + t * direction.x

我们要找出 t 的范围,使得这个 t 落在盒子的 X、Y、Z 范围内。

在每个轴上:

  1. 计算 ray 和两个 slab 面(min、max)的相交时间(t 值)
  2. 找到所有轴中进入的最大 t 和退出的最小 t
  3. 如果这两个值交叉了,恭喜你!光线穿过盒子!

🧠 实现:Slab 法代码 JavaScript 版

function intersectRayAABB(origin, direction, boxMin, boxMax) {
  let tMin = -Infinity;
  let tMax = Infinity;

  for (const axis of ['x', 'y', 'z']) {
    const o = origin[axis];
    const d = direction[axis];
    const min = boxMin[axis];
    const max = boxMax[axis];

    if (Math.abs(d) < 1e-8) {
      // 光线与轴平行
      if (o < min || o > max) return false; // 永远不可能进入
    } else {
      const t1 = (min - o) / d;
      const t2 = (max - o) / d;
      const tEnter = Math.min(t1, t2);
      const tExit = Math.max(t1, t2);
      tMin = Math.max(tMin, tEnter);
      tMax = Math.min(tMax, tExit);
    }
  }

  return tMax >= tMin && tMax >= 0;
}

🧪 示例调用:

const boxMin = { x: -1, y: -1, z: -1 };
const boxMax = { x:  1, y:  1, z:  1 };

const origin = { x: 0, y: 0, z: -5 };
const direction = { x: 0, y: 0, z: 1 };

console.log(intersectRayAABB(origin, direction, boxMin, boxMax)); // true ✅

🧾 核心步骤回顾

  1. 每轴计算进入 / 退出 t 值
  2. 更新全局 tMin(最大进入)和 tMax(最小退出)
  3. 如果 tMin > tMax 或 tMax < 0 → 没交集

📈 为什么叫“三次乘法”?

这是因为最核心的计算是:

(min - origin) / direction
(max - origin) / direction

对于每个轴,做一次乘除操作(可变形为乘法:除以 d 相当于乘以 1/d),所以我们说:三次乘法决定交与否。

🎭 若你用 invDir = 1 / dir 预计算后重用,甚至可以避免除法开销,快得像穿墙!


💡 延伸思考

  • 光线可能从内部射出吗?

    • 当然可以,tMin < 0 但 tMax > 0,依然命中。
  • 如何返回相交的最近点?

    • 直接返回 tMin 即可:intersection = origin + direction * tMin
  • 可以用在 BVH、Octree 中?

    • 完美适配,简洁又高效!

🧙 一句话总结

Slab 法就像一位空间裁缝,它不问光线的情感,只根据三条正义之轴,决定命运的交点是否存在。


“你看似在检查盒子,实则盒子也在衡量你。”
—— 图形学圣经·BVH卷·Slab章