从零推导射线与平面、射线与球体的交点公式,配合交互可视化、逐步数学推导,以及平行射线和负 t 值等边界情况的实用处理技巧。
什么是射线?
在现实生活中,我们通常将射线描述为从一个点出发,沿某个方向无限延伸的路径。
我们可以用数学方式来表达这个定义。
射线从一个点出发,我们记为 ,并沿着某个特定方向 延伸。
由于计算机无法表示“无限”,我们引入一个参数 ,表示射线上的长度。
射线与平面求交
平面是讨论求交问题的一个很好的起点,一个常见的问题是:我们如何判断一条射线是否与一个平面相交?
基本思路是,如果射线与平面相交,那么交点 必须位于该平面上。
根据射线的定义,我们可以表示交点 :
另一方面,我们知道,如果一个点在平面上,那么该点与平面法向量 的点积应为零:
因此,我们可以将 的表达式代入点积公式中:
这个判断射线是否与平面相交的问题,就转化为求解未知数 。
如果这个方程有解,那么就说明射线与平面存在交点。
那平面的法向量 是怎么来的呢?
我们可以假设平面固定在原点,并朝上方朝向。当需要变换时,可以使用矩阵对平面进行变换。当前情况下,平面的法向量可以取为 。
接下来,我们将用代数方法求解这个方程。
逐步求解
我们从射线与平面相交的条件出发:
展开点积为两个部分:
利用数量乘法的结合律,将 提出点积之外:
接着,我们从两边同时减去 ,以便将含 的项单独列出:
最后,将两边同时除以 ,得到 的解:
这个标量 表示沿射线方向前进多远会与平面相交。
边界情况:负 值与平行射线
在这个求交方程中,有两种情况是无效的:
如果 为负值,说明平面在射线的后方。
可以想象我们就是射线,朝着 的方向前进,此时一个负的 表示平面在我们身后,在相交的语义下这是没有意义的,因此这种情况被视为无效。
另一种无效情况是分母为零。
这意味着射线的方向与平面平行,此时 ,会导致除以零的错误,不仅在数学上无解,在程序中也可能引发编译错误或运行时异常。
因此,这种情况也必须在实现中加以处理。
总结一下:我们只在 的范围内,才认为这个交点是有效的。
射线与球体求交
众所周知,如果一个点位于球面上,那么该点 到球心 的距离的平方必须等于球半径的平方 。也就是说:
这个式子实际上就是点 到球心之间的欧几里得距离的平方。我们可以用向量的方式更简洁地表示为:
其中 表示球心的位置。由于距离是非负的,两边同时开平方根得到:
为了简化分析,我们假设球心位于原点,即 。此时公式进一步简化为:
根据定义,向量 的长度为:
因此,我们的关系式可以写成:
为了简化运算,我们对等式两边平方,消掉平方根:
接下来,和之前一样的套路:把 替换成射线的定义 ,于是得到:
你可能已经猜到了,点积具有分配律:
实际上这正是一个完全平方公式:
你可能觉得我们提取 的步骤有点快,但别担心, 是一个标量,之前我们也做过类似处理,这样操作是完全合法的。 我们已经知道射线的全部信息,即 是射线的起点, 是射线的方向,同时也知道球体的半径 。
因此,这个公式就变成了关于未知数 的一个二次方程。
为了更清晰地看出它的结构,我们可以按下面这种顺序写出它:
我们可以使用二次方程公式来求解:
在这个问题中,我们的各项对应如下:
老实说,这个公式我们不一定要把它完全展开。
但如果你真的感兴趣,下面就是展开后的表达式:
希望你没被吓到——其实我很想用颜色来标注关键项,可惜 KaTeX 不支持 :( 不过不妨碍我们继续。
两个解,一个交点 —— 为什么用 min()
首先要明确:如果我们选择解析解法,那么每一帧都需要重新计算这个方程。
因此,第一个检查点就是根号里的内容(判别式)不能小于零,因为我们这里不涉及复数解。
然后,没错——我们实际上会得到两个解。
想象一下:射线第一次击中球体的地方是进入点,而之后还会有一个离开点。
所以在实现时,我们只关心最靠近的一次交点。
这就是为什么在代码中通常会使用 min() 函数来取最小的那个解。
今天就到这里啦——希望你觉得内容有趣又实用!
下次见!