矢量是一个基本的数学概念,用于描述方向和大小。在游戏和应用程序中,矢量通常用于描述一些基本属性,例如角色的位置、物体移动的速度或两个对象之间的距离。
矢量算术是计算机编程很多方面(如图形、物理和动画)的基础,深入了解这一主题对于充分发挥 Unity 的功能很有帮助。
矢量可以在多个维度上表示,Unity 提供 Vector2、Vector3 和 Vector4 类来处理 2D、3D 和 4D 矢量。 这三个类型的矢量类都共享许多相同的功能,例如大小,因此除非另有说明,否则本页上的大部分信息都适用于所有三个类型的矢量。
本页面概述矢量类及其使用这些类编写脚本时的常见用法。有关矢量类每个成员的详尽参考,请参阅脚本参考页面中的 Vector2、Vector3 和 Vector4。
当两个矢量相加时,结果相当于将原始矢量依次作为“步骤”。请注意,两个参数的顺序无关紧要,因为两种方式的结果都相同。
如果将第一个矢量视为空间中的一个点,那么第二个矢量可以解释为从该位置的偏移或“跳跃”。例如,为了找到地面上某个位置上方 5 个单位的点,可使用以下计算:
var pointInAir = pointOnGround + new Vector2(0, 5);
如果矢量代表力,那么从力的方向和大小来考虑它们将会更直观。两个力矢量相加会产生一个等于力的组合的新矢量。施加的力同时有若干单独分量起作用时(例如,向前推进的火箭还可能受到侧风影响),此概念通常很有用。
尽管此处的示例显示的是 2D 矢量,但相同的概念也适用于 3D 和 4D 矢量。
矢量减法通常用于获取从一个对象到另一个对象的方向和距离。请注意,两个参数的顺序对于减法很__重要__:
// 矢量 d 的大小与 c 相同,但指向相反的方向。
var c = b - a;
var d = a - b;
与数字一样,与负向矢量相加相当于减去正向矢量。
// 这两者得出相同的结果。
var c = a - b;
var c = a + -b;
负向矢量的大小与原始矢量相同并沿着同一条线指向,但在完全相反的方向上。
如果从空间中的一个点减去另一个点,则得到的结果是从一个对象“指向”另一个对象的矢量:
// 获取从玩家位置指向目标位置的矢量。
var heading = target.position - player.position;
除了指向目标对象的方向之外,该矢量的大小等于两个位置之间的距离。您需要“归一化”矢量来提供目标的方向,但距离固定(例如用于指挥飞弹)。可以将矢量除以其大小,从而将其归一化:
var distance = heading.magnitude;
var direction = heading / distance; // This is now the normalized direction.
此方法优于单独使用大小和归一化属性,因为大小和归一化属性都非常耗费 CPU(都涉及计算平方根)。
如果只需要使用距离进行比较(例如,进行接近检查),则可以完全避免大小计算。sqrMagnitude 属性给出大小值的平方,计算方式与大小相似,但不需要进行耗时的平方根运算。不要将大小与已知距离进行比较,可将大小的平方与距离的平方进行比较:
if (heading.sqrMagnitude < maxRange * maxRange) {
// 目标在范围内。
}
这种算法比在比较中使用真实大小要高效得多。
有时,在 3D 中工作时,可能需要一个到目标的“平行于地面的方向”。例如,想象一个站在地面上的玩家需要接近漂浮在空中的目标。如果从目标位置减去玩家位置,那么产生的矢量将向上指向目标。这种情况下不适合对玩家变换进行定向,因为变换也会指向上方;真正需要的是从玩家位置到目标正下方地面位置的矢量。通过使用减法结果并将 Y 坐标设置为零,可以获得该矢量:
var heading = target.position - player.position;
heading.y = 0; // 这是平行于地面的方向。
在讨论矢量时,通常将普通数(例如,浮点值)称为标量。这意味着标量只有“标度”或大小,而矢量兼具大小和方向。
将矢量乘以标量会产生与原始矢量方向相同的矢量。但是,新矢量的大小等于原始大小乘以标量值。
同样,标量除法将原始矢量的大小除以标量。
当矢量表示移动偏移或力时,这些运算很有用。通过这些运算可以更改矢量的大小而不影响其方向。
当任何矢量除以其自身的大小时,得到的结果是大小为 1 的矢量,即所谓的归一化矢量。如果归一化矢量乘以标量,则结果的大小将等于该标量值。当力的方向恒定但强度可控时(例如,来自车轮的力总是向前推动,但是动力由驾驶员控制),这会很有用。
点积取两个矢量并返回标量。该标量等于两个矢量相乘的大小,得到的结果再乘以矢量之间角度的余弦。当两个矢量都被归一化时,余弦本质上表示第一个矢量在第二个矢量的方向上延伸的距离(反之亦然 - 参数的顺序无关紧要)。
下面对与参考矢量相比不同角度的矢量如何返回介于 1 和 –1 之间的点积值进行对比:
点积是一种比余弦更简单的数学运算,因此在某些情况下可用于代替 Mathf.Cos 函数或矢量大小运算(功能不完全相同但有时效果相同)。但是,计算点积函数所需的 CPU 时间要少得多,因此可作为一种有价值的优化。
如果需要计算一个矢量在另一个矢量方向上的大小,点积很有用。
例如,汽车的速度计一般用于测量车轮的转速。汽车可能不会直接向前移动(例如,可能侧向打滑),在此情况下,部分运动不是朝向汽车前方,因此速度计无法测量。对象的 rigidbody.velocity 矢量的大小将给出整体运动方向上的速度,但是为了单独考虑向前的速度,应使用点积:
var fwdSpeed = Vector3.Dot(rigidbody.velocity, transform.forward);
当然,方向可以是您喜欢的任何方向,但为了进行此计算,必须对方向矢量进行归一化。这样,不仅结果比速度大小更准确,而且无需执行在查找大小时需要的缓慢的平方根运算。
叉积仅对 3D 矢量有意义。它需要两个 3D 矢量作为输入,并返回另一个 3D 矢量作为结果。
结果矢量垂直于两个输入矢量。可使用“左手规则”根据输入矢量的排序确定输出矢量的方向。如果第一个参数对应于手的拇指,而第二个参数对应于食指,则结果将指向中指的方向。如果参数的顺序颠倒,得到的矢量将指向完全相反的方向,但大小相同。
结果的大小等于输入矢量的大小相乘,然后该值再乘以二者之间角度的正弦。正弦函数的一些有用值如下所示:
叉积看起来很复杂,因为它在返回值中结合了多方面的有用信息。然而,就像点积一样,它在数学上的效率非常高,可用于优化代码,否则这些代码将不得不依赖于更缓慢的超越函数,如正弦和余弦函数。
在网格生成期间经常需要“法向”矢量(即,垂直于平面的矢量),此外,法向矢量也用于路径跟踪和其他情况。在平面中给定三个点(比如网格三角形的角点),就很容易找到法线,如下所示: - 选择三个点之一 - 分别从其他两个点中的每一个中减去它(产生两个新矢量,“Side 1”和“Side 2”) - 计算矢量“Side 1”和“Side 2”的叉积 - 叉积的结果是一个新的矢量,它垂直于三个原始点所在的平面,也就是“法线”。
Vector3 a;
Vector3 b;
Vector3 c;
Vector3 side1 = b - a;
Vector3 side2 = c - a;
Vector3 normal = Vector3.Cross(side1, side2);
使用“左手规则”可确定将这两个矢量传递到叉积函数的顺序。在表面上方从上往下看(法线将指向外部)时,第一个矢量应顺时针扫过第二个矢量:
如果输入矢量的顺序颠倒,结果将指向完全相反的方向。
对于网格,法向矢量也必须归一化。可通过归一化属性来实现此目的,但是还有另一个偶尔有用的技巧。还可以将垂直矢量除以其大小,从而将其归一化:
float perpLength = perp.magnitude;
perp /= perpLength;
另外需要注意的是,三角形的面积等于 perpLength / 2。如果需要得出整个网格的表面积,或者想要根据相对面积随机选择三角形,这会很有用。