在实时物理模拟与碰撞检测领域,GJK(Gilbert-Johnson-Keerthi)算法堪称经典中的经典。它通过迭代计算凸体间的支撑函数,高效判断两个凸多面体是否相交。然而,当物体遭遇非均匀缩放(即沿不同轴向具有不同缩放因子)时,许多开发者困惑地发现:用逆矩阵变换方向向量会导致错误结果,而转置矩阵却始终有效。这背后隐藏着深刻的线性代数原理,理解它对于编写健壮的碰撞检测代码至关重要。

支撑函数的本质:方向与点的对偶关系

GJK算法的核心在于“支撑函数”(Support Function),它接受一个方向向量,返回该方向上凸体的最远点。数学上,对于凸体A,支撑函数定义为:

[ S_A(d) = \arg\max_{p \in A} (d \cdot p) ]

这个点积最大化操作建立了方向与点之间的对偶关系。当我们对物体施加仿射变换(如旋转、平移、缩放)时,世界空间中的凸体变为 ( M \cdot A = {M p \mid p \in A} ),其中M是3×3仿射变换矩阵(忽略平移,因为平移不影响方向变换)。此时,世界空间中的支撑函数需求变成:给定世界方向d,找到变换后凸体上使 ( d \cdot (M p) ) 最大的点p_local。

为什么转置矩阵能保持方向一致

将点积展开:( d \cdot (M p) = d^T M p = (M^T d)^T p )。这意味着,最大化 ( d \cdot (M p) ) 等价于在局部空间中最大化 ( (M^T d) \cdot p )。因此,算法可以这样做:

  1. 将世界方向d用转置矩阵 ( M^T ) 变换到局部方向 ( d_{local} = M^T d )。
  2. 在局部坐标系中计算支撑点 ( p_{local} = S_A(d_{local}) )。
  3. 将局部点用M变换回世界坐标 ( p = M p_{local} )。

这个流程对任意仿射变换(包括非均匀缩放)都成立,因为转置操作精确保持了点积的线性对应关系。

逆矩阵为何在非均匀缩放时崩溃

许多开发者直觉上认为,应该用逆矩阵来“反向”变换方向,就像逆矩阵用于变换法线一样。确实,对于法线向量(其与表面切线正交),正确的变换矩阵是 ( M^{-T} )(逆的转置)。但方向向量不同——它不是一个几何法线,而是一个用于点积最大化的“搜索方向”。

当我们尝试用逆矩阵 ( M^{-1} ) 变换方向时,局部方向变为 ( d'_{local} = M^{-1} d )。此时,点积关系是:

[ d \cdot (M p) = (M^T d) \cdot p \neq (M^{-1} d) \cdot p \quad \text{除非} \quad M^T = M^{-1} ]

而 ( M^T = M^{-1} ) 成立的条件是M为正交矩阵——即仅包含旋转(可能加反射),不包含缩放。当非均匀缩放引入时,M不再正交,因此 ( d'{local} ) 与正确方向 ( d变换为(0.5,0),而被M^T变换为(2,0)。这两个方向指向同一个轴但缩放因子不同,导致计算出的支撑点位置完全不同,最终碰撞检测结果错误。} ) 发生偏差。例如,假设M在X轴缩放2倍、Y轴缩放0.5倍,那么沿世界X轴的方向d=(1,0)会被M^{-1

实践中的注意事项

在实现GJK时,正确的做法是: - 对于方向向量(用于搜索),始终使用变换矩阵的转置(或等价地,将方向作为行向量左乘矩阵)。 - 对于法线向量(如计算碰撞法线),使用逆矩阵的转置。 - 如果变换包含平移,需要将平移分量分离,因为方向向量是零阶张量,不受平移影响。

此外,若矩阵包含投影变换(如透视投影),线性关系不再适用,需采用更复杂的处理。但在常见的仿射变换场景下,牢记“转置优先”即可避免非均匀缩放带来的陷阱。

结语

GJK算法的优雅之处在于它将几何问题转化为线性代数运算。理解转置矩阵为何工作、逆矩阵为何失效,不仅能让开发者避开常见的Bug,更能深化对向量变换本质的认识:方向向量是协变向量,其变换规律与逆变向量(如位置点)不同。下次当你为碰撞检测编写代码时,不妨检查一下——你是否在不知不觉中误用了逆矩阵?毕竟,在非均匀缩放的世界里,转置才是真正的“朋友”。