对于类似《密特罗德 生存恐惧》和《暗影火炬城》这样 3D 场景,但玩法还是 2D 卷轴动作平台跳跃(类银河恶魔城)的游戏, 如果想要让碰撞检测更符合视觉直觉,需要采用 3D 碰撞体来模拟 2D 碰撞。本文将介绍一种实现方案。
方法概述
为了简化碰撞计算,原碰撞体(如武器的碰撞)只使用长方体(OBB)和球(sphere)。对于立方体,
- 首先将 8 个顶点垂直投影到与相机到碰撞体的连线垂直的碰撞体。
- 根据同一平面的 8 个顶点生成一个包围点云的误差最小的二维 OBB 。
- 将 2 维 OBB 沿着法线方向(碰撞体到相机的连线)前后拉伸足够长形成一个长方体,以使其能够与景深不同的其他碰撞体发生碰撞。
对于球碰撞体也采用类似的方法,只是无需通过顶点生成包围盒这一步,且拉伸后的结果为胶囊体。如果想要模拟较复杂的碰撞体,可以将其简化为多个基础碰撞体的组合再进行处理。
接下来讲解具体每一步。
顶点到平面的垂直投影
8 个顶点投影基本算法如图所示,平面
P
P
P 的单位法线向量为
n
n
n ,
p
p
p 为平面上任一点。
求任意点 a a a 到平面 P P P 的投影点的向量解法为:
- 求向量 ( a − p ) (\boldsymbol a-\boldsymbol p) (a−p) 在 n \boldsymbol n n 方向上的投影: d = n ⋅ ( a − p ) d = \boldsymbol n\cdot(\boldsymbol a-\boldsymbol p) d=n⋅(a−p)
- 投影点 a ′ a^\prime a′ 即为: a ′ = a − d n a^\prime =\boldsymbol a - d\boldsymbol n a′=a−dn
求解最小降维 OBB
最小OBB 就是包围点云最小体积的 Bounding Box 的方法。采用的方法为主成分分析。
主成分分析(PCA)
主成分分析(Principal Component Analysis, PCA) 是一种常用的降维技术,旨在通过特征值和特征向量将高维数据转换为低维数据,同时尽量保留原始数据的方差(信息)。
PCA 的步骤:
-
计算协方差矩阵:计算数据集的协方差矩阵,捕捉数据中各特征之间的相关性。
-
求特征值和特征向量:计算协方差矩阵的特征值和特征向量。特征向量表示新的基向量(主成分方向),特征值表示这些方向上的方差大小(信息量)。
-
选择主成分:按特征值的大小对特征向量进行排序,选择特征值较大的几个特征向量作为主成分。这些主成分对应了数据集的主要变化方向。
-
转换数据:将原始数据投影到这些主成分上,从而实现降维。
下面介绍一下协方差矩阵。
协方差矩阵
协方差矩阵是一个方阵,其中每个元素代表了一对变量之间的[[协方差]]。对于随机向量
X
=
(
X
1
,
X
2
,
.
.
.
,
X
n
)
X = (X_1, X_2, ..., X_n)
X=(X1,X2,...,Xn),其协方差矩阵
Σ
\Sigma
Σ 定义为:
Σ
=
[
σ
1
,
1
σ
1
,
2
⋯
σ
1
,
n
σ
2
,
1
σ
2
,
2
⋯
σ
2
,
n
⋮
⋮
⋱
⋮
σ
n
,
1
σ
n
,
2
⋯
σ
n
,
n
]
\Sigma = \begin{bmatrix} \sigma_{1,1} & \sigma_{1,2} & \cdots & \sigma_{1,n} \\ \sigma_{2,1} & \sigma_{2,2} & \cdots & \sigma_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ \sigma_{n,1} & \sigma_{n,2} & \cdots & \sigma_{n,n} \end{bmatrix}
Σ=
σ1,1σ2,1⋮σn,1σ1,2σ2,2⋮σn,2⋯⋯⋱⋯σ1,nσ2,n⋮σn,n
其中,
σ
i
,
j
\sigma_{i,j}
σi,j 是
X
i
X_i
Xi 和
X
j
X_j
Xj 之间的协方差,计算公式为:
σ
i
,
j
=
E
[
(
X
i
−
μ
i
)
(
X
j
−
μ
j
)
]
\sigma_{i,j} = E[(X_i - \mu_i)(X_j - \mu_j)]
σi,j=E[(Xi−μi)(Xj−μj)]
μ
i
\mu_i
μi 和
μ
j
\mu_j
μj 分别是
X
i
X_i
Xi 和
X
j
X_j
Xj 的期望值。
协方差矩阵是一种统计量,描述点云的方向分布和扩展。它的主方向(最大特征值对应的特征向量)可以用来估计点云的主要方向,进而可以确定 OBB 的方向和大小。
求矩阵特征值
以下是几种主要的求解矩阵特征值和特征向量数值方法的对比。这些方法各有优劣,适用场景也不同,具体对比如下:
方法 | 适用矩阵类型 | 特征值求解 | 特征向量求解 | 优点 | 缺点 |
---|---|---|---|---|---|
QR 分解法 | 任意矩阵 | 所有特征值 | 所有特征向量 | 能求所有特征值和特征向量,适用于稠密矩阵 | 实现复杂度高,计算量大,适用于中小型矩阵 |
Jacobi 方法 | 对称矩阵 | 所有特征值 | 所有特征向量 | 简单易实现,适合小型对称矩阵,数值稳定 | 仅适用于对称矩阵,效率低于 QR 法 |
分而治之法 | 对称矩阵 | 所有特征值 | 所有特征向量 | 高效,适合对称矩阵,能并行计算 | 实现复杂,适用性较窄(仅对称矩阵) |
Lanczos 方法 | 大型稀疏矩阵 | 部分特征值 | 部分特征向量 | 高效,内存消耗低,适用于大规模稀疏矩阵 | 数值稳定性差,需稳定化处理,不适合稠密矩阵 |
因为要求解的矩阵是 3 维协方差矩阵,属于小型对称矩阵,并且需要稳定高精度的解,所以推荐使用 Jacobi 方法。
Jacobi 方法
Jacobi 方法的核心思想是不断应用旋转变换来消除矩阵中的非对角元素,逐步将矩阵逼近一个对角矩阵。每次迭代选择一个最大的非对角元素,将它变为零,最终,矩阵的对角元素就是特征值,对角化过程中形成的旋转矩阵乘积则为特征向量矩阵。
关键步骤包括:
- 找到最大非对角元素。
- 计算旋转角度并执行旋转。
- 更新矩阵和特征向量矩阵。
- 检查终止条件。
因为最终结果应为 OBB,所以可以对求解出的特征向量采用施密特正交化(Schmidt Orthogonalization)来进行正交化。
OBB 拉伸方法
考虑相机是透视视角的,投影OBB正常应该拉伸成一个截头锥体(frustum),但 frustrum 的碰撞计算过于耗费性能。又由于透视效果并不是很大,并且保证碰撞体景深方向距离尽可能小,所以替换为使用立方体来模拟。也是因为透视,在碰撞体原位置进行投影后拉伸即能保证最正确的视觉效果。
需要注意的一点时,会进行碰撞检测的任意两个碰撞组之间只需要有一方进行投影拉伸即可保证碰撞事件的触发,另一方也可以采用一些精度较高精度的碰撞体。