Unity3D数学第二篇:旋转与欧拉角、四元数(核心变换篇)
在 3D 游戏开发的世界里,数学并非枯燥的公式堆砌,而是构建虚拟世界的基石。每一个角色的移动,每一束光的折射,每一次碰撞的发生,都离不开 3D 数学的支撑。而在这所有宏伟景象的背后,最基础也是最核心的砖石,就是向量 (Vector)。
本篇将带你深入了解向量的本质,它与点、线、面这些基本几何元素的联系,以及如何在 Unity 或 Unreal Engine 中高效、准确地运用它们。我们不仅会探讨基本运算,还会揭示其背后的几何意义,并穿插一些更深层次的思考,让你从一开始就建立起扎实而全面的 3D 数学基础。
1. 概念初探:点与向量的本源
在 3D 空间中,我们首先要区分两个最基本的概念:点 (Point) 和 向量 (Vector)。它们虽然都用 (x, y, z)
这样的坐标形式表示,但其几何意义和在游戏中的用途却截然不同。
1.1 点:空间中的位置
点,顾名思义,是 3D 空间中的一个具体位置。它没有大小、没有方向,仅仅是空间中的一个占位符。想象你在房间里用手指指着天花板上的某个特定位置,那个“位置”就是一个点。
在 Unity 中,我们通常使用 Vector3
类型来表示一个点,例如:
C#
Vector3 playerSpawnPoint = new Vector3(0, 10, 0); // 玩家的出生点在世界坐标(0, 10, 0)
这里的 playerSpawnPoint
就是一个点,它明确定义了空间中的一个绝对位置。
1.2 向量:方向与大小的结合
向量则是一个既有方向 (Direction) 又有大小 (Magnitude) 的量。它描述的是一个位移 (Displacement) 或者一种趋势。你可以把它想象成一支箭:箭头的方向指示了向量的方向,箭杆的长度则代表了向量的大小。
重要的是,向量没有固定的起点。一个从 (0,0,0)
指向 (1,2,3)
的向量,和一个从 (10,10,10)
指向 (11,12,13)
的向量,只要它们的方向和长度完全相同,它们就是同一个向量。这正是向量与点最核心的区别:点表示绝对位置,向量表示相对位移或方向。
在 Unity 和 Unreal Engine 中,向量也用 Vector2
(2D 向量) 或 Vector3
(3D 向量) 表示。例如:
C#
// 一个表示向上方向的向量,长度为 1
Vector3 upDirection = Vector3.up; // 等同于 new Vector3(0, 1, 0)
// 一个表示从原点到 (3, 4, 0) 的位移向量
Vector3 displacement = new Vector3(3, 4, 0);
在游戏开发中,向量无处不在:
-
移动方向:角色前进的方向。
-
力:作用在物体上的推力或拉力。
-
速度:物体移动的速度和方向。
-
光线方向:光源发出的光线方向。
-
法线:表面朝向的方向。
2. 向量的基本运算:构建 3D 世界的基石
理解了向量的本质,我们就可以开始学习它的基本运算。这些运算是你在游戏中实现位移、力学、方向判断等功能的核心工具。
2.1 向量的加法与减法:位移的叠加与方向的求取
向量的加减法是实现物体移动和位置关系判断的基础。
2.1.1 向量加法 (A + B
)
-
几何意义: 两个位移的叠加。如果你先沿着向量
A
的方向和长度移动,然后再沿着向量B
的方向和长度移动,那么你最终相对于起点的总位移,就是A + B
这个新向量。这在几何上可以用“平行四边形法则”或“三角形法则”来表示。 -
计算方式: 对应分量相加。
如果 A=(A_x,A_y,A_z) 且 B=(B_x,B_y,B_z),
那么 A+B=(A_x+B_x,A_y+B_y,A_z+B_z)。
-
Unity/UE 应用:
-
角色移动: 当角色在地面上向前方移动,同时又受到向上跳跃的力时,它的总位移就是前方向量和跳跃向量的叠加。
C#
// Unity transform.position += moveDirection * speed * Time.deltaTime; // 角色每一帧的位移累加
-
多个力的合力: 一个物体同时受到重力和推力,其合力是这两个力向量的叠加。
-
2.1.2 向量减法 (B - A
)
-
几何意义: 从向量
A
的终点指向向量B
的终点的向量。它表示了从点A
到点B
的位移。 -
计算方式: 对应分量相减。
如果 A=(A_x,A_y,A_z) 且 B=(B_x,B_y,B_z),
那么 B−A=(B_x−A_x,B_y−A_y,B_z−A_z)。
-
Unity/UE 应用:
-
计算方向向量: 这是最常见的用途。如果你想让一个物体
A
朝向另一个物体B
移动,你需要知道从A
到B
的方向。C#
// Unity Vector3 directionToTarget = targetTransform.position - myTransform.position; // directionToTarget 就是从当前物体指向目标物体的向量
-
相对位置: 计算一个物体相对于另一个物体的位置。
-
1.1.3 重要知识点:向量空间的加法封闭性
从数学的角度来看,向量的加法运算满足封闭性。这意味着两个向量相加的结果仍然是一个向量。这听起来理所当然,但在更复杂的数学结构中,并不是所有运算都满足封闭性。例如,两个整数相除可能得到浮点数,就不满足整数集合的封闭性。向量的这种性质保证了我们在 3D 空间中进行位移合成时,始终能得到一个有意义的位移结果。
2.2 标量乘法与除法:改变向量的长度
标量 (Scalar) 是一个只有大小没有方向的量,比如一个普通的数字(float
或 int
)。
2.2.1 标量乘法 (k * A
或 A * k
)
-
几何意义: 改变向量
A
的长度。-
如果
k > 1
,向量变长。 -
如果
0 < k < 1
,向量变短。 -
如果
k = 0
,向量变为零向量(长度为0,方向不定)。 -
如果
k < 0
,向量方向反转,并改变长度。
-
-
计算方式: 向量的每个分量都乘以标量 k。
如果 A=(A_x,A_y,A_z),
那么 kcdotA=(kcdotA_x,kcdotA_y,kcdotA_z)。
-
Unity/UE 应用:
-
控制速度:
moveDirection * speed
,speed
就是一个标量,它决定了移动向量的长度,从而控制移动的速度。 -
缩放: 均匀缩放一个物体时,本质上就是对它的所有局部坐标点(可以看作从原点出发的向量)进行标量乘法。
-
力的大小:
directionOfForce * forceMagnitude
。
-
2.2.2 标量除法 (A / k
)
-
几何意义: 等同于乘以
1/k
,将向量的长度缩小k
倍。 -
计算方式: 向量的每个分量都除以标量
k
。
2.3 向量的模(长度)与单位向量:量化与纯化方向
向量的长度和方向是其最重要的两个属性。
2.3.1 向量的模(长度,Magnitude)
-
几何意义: 向量的几何长度。在 3D 空间中,一个向量 A=(A_x,A_y,A_z) 的长度是其各分量平方和的平方根,这来源于勾股定理在三维空间的扩展。
∣A∣=sqrtA_x2+A_y2+A_z2
-
Unity/UE API:
-
vector.magnitude
:返回向量的实际长度。 -
vector.sqrMagnitude
:返回向量长度的平方。
-
-
重要知识点:sqrMagnitude 的性能优化
为什么 Unity 提供了 sqrMagnitude?因为开方运算 (Square Root) 是一个相对耗费 CPU 性能的操作。在很多情况下,我们并不需要知道向量的精确长度,只需要比较两个向量的长度大小,或者判断一个向量的长度是否超过某个阈值。
例如,判断两个物体距离是否小于 5 个单位:
C#
Vector3 obj1Pos = obj1.transform.position; Vector3 obj2Pos = obj2.transform.position; float distance = Vector3.Distance(obj1Pos, obj2Pos); // 内部会进行开方运算 if (distance < 5f) { /* ... */ } // 优化后的写法:比较距离的平方 Vector3 displacement = obj2Pos - obj1Pos; float sqrDistance = displacement.sqrMagnitude; // 没有开方运算 if (sqrDistance < 5f * 5f) { // 5f * 5f = 25f Debug.Log("两物体距离小于 5 个单位,性能更好!"); }
通过使用
sqrMagnitude
,我们避免了不必要的开方运算,这在游戏循环中频繁进行距离判断时能带来显著的性能提升。这是一种非常重要的微优化 (Micro-optimization) 技巧。
2.3.2 单位向量 (Normalized Vector)
-
几何意义: 长度为 1 的向量。它只保留了向量的方向信息,而不包含任何长度信息。
将一个非零向量标准化(或归一化)的操作,就是将它除以它的模。
U_A=A/∣A∣
-
Unity/UE API:
-
vector.normalized
:返回当前向量的单位向量副本(新向量)。 -
vector.Normalize()
:原地将当前向量修改为单位向量(不返回新向量)。
-
-
应用:
-
表示方向: 光的方向、法线(表面朝向)、角色面向的方向、施加力的方向等,都应该使用单位向量。
-
标准化速度: 当你希望物体沿着某个方向以固定速度移动时,你需要确保方向向量是单位向量,然后乘以你的速度标量。
C#
Vector3 inputDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); // 确保移动方向的长度不会因为同时按下了A和W而比单独按A长 Vector3 moveDirection = inputDirection.normalized; float speed = 5f; transform.position += moveDirection * speed * Time.deltaTime;
如果没有
normalized
,斜向移动的速度会比直线移动快(因为向量长度是 sqrtx2+y2)。
-
3. 向量的乘法:点积与叉积的深度解析
虽然统称为“乘法”,但向量的点积和叉积在几何意义和应用上有着天壤之别,是 3D 数学中最重要的两个运算。
3.1 点积 (Dot Product)
-
Unity/UE API:
Vector3.Dot(vectorA, vectorB)
-
计算方式: 对应分量相乘后求和。结果是一个标量。
如果 A=(A_x,A_y,A_z) 且 B=(B_x,B_y,B_z),
那么 AcdotB=A_xB_x+A_yB_y+A_zB_z。
-
几何意义:
点积的核心意义是衡量两个向量方向的相似程度或**“对齐”程度**。它与两个向量的长度以及它们之间夹角的余弦值 (Cosine) 有关。
AcdotB=∣A∣∣B∣cos(theta)
其中 theta 是向量 A 和 B 之间的夹角。
-
当 A 和 B 都是单位向量时:
点积的结果直接就是 cos(theta)。
-
Dot(A, B) = 1
:向量方向完全相同(夹角 0 度)。 -
Dot(A, B) = 0
:向量相互垂直(夹角 90 度)。 -
Dot(A, B) = -1
:向量方向完全相反(夹角 180 度)。 -
Dot(A, B) > 0
:夹角小于 90 度(大致同向)。 -
Dot(A, B) < 0
:夹角大于 90 度(大致反向)。
-
-
-
应用:
-
视野判断 (Field of View - FoV): 判断一个物体是否在玩家的前方视野内。
C#
// Unity Vector3 toTarget = (target.position - player.position).normalized; // 从玩家指向目标的单位向量 float dotProduct = Vector3.Dot(player.transform.forward, toTarget); // 玩家前方向量与指向目标向量的点积 // 如果 dotProduct > 0.5 (对应约 60 度 FoV,cos(60度) = 0.5),则在前方视野内 if (dotProduct > Mathf.Cos(playerFoVAngle / 2 * Mathf.Deg2Rad)) { Debug.Log("目标在玩家前方视野内!"); }
这是游戏 AI 追逐、攻击判断、敌人生成等功能的核心逻辑。
-
光照计算: 在 3D 渲染中,光线对物体表面的影响取决于光线方向与表面法线(一个单位向量)的夹角。点积正是计算这个夹角余弦值的理想工具。夹角越小(光线越垂直于表面),表面接收到的光照强度越大。
-
判断物体是否面对彼此: 两个 AI 角色是否“看到”了对方。
-
物理效果: 判断碰撞的相对方向(例如,一个球体是否撞到了地面的“上面”)。
-
3.1.1 重要知识点:点积与向量投影
点积的另一个重要几何解释是向量投影 (Vector Projection)。
AcdotB=∣A∣cdot(∣B∣cos(theta))
这可以理解为向量 A 的长度乘以向量 B 在向量 A 方向上的投影长度。
或者更常见地:如果 B 是一个单位向量 u,那么 Acdotu=∣A∣cos(theta),这表示向量 A 在单位向量 u 方向上的投影长度。
-
应用:
-
在 Unity 中模拟沿着斜坡的重力: 当一个角色在斜坡上时,重力
Vector3.down
并非完全垂直于斜坡表面。我们可以将重力向量投影到斜坡的法线方向上,得到垂直于斜坡的分量;再将重力向量投影到斜坡表面上(与法线垂直),得到沿着斜坡向下滑的分量。 -
Vector3.Project(vector, onNormal)
:将vector
投影到onNormal
方向上。 -
Vector3.ProjectOnPlane(vector, planeNormal)
:将vector
投影到由planeNormal
定义的平面上。
-
这在实现更真实的物理交互和角色控制器时非常有用。
3.2 叉积 (Cross Product)
-
Unity/UE API:
Vector3.Cross(vectorA, vectorB)
-
计算方式: 结果是一个向量。这个计算比较复杂,通常不需要手动记住,但知道其过程可以加深理解。
如果 A=(A_x,A_y,A_z) 且 B=(B_x,B_y,B_z),
那么 AtimesB=(A_yB_z−A_zB_y,A_zB_x−A_xB_z,A_xB_y−A_yB_x)。
-
几何意义:
叉积的结果是一个垂直于两个输入向量 A 和 B 的新向量。
-
方向: 遵循右手定则 (Right-Hand Rule)。如果你用右手的手指从第一个向量
A
弯曲到第二个向量B
,你的大拇指指向的方向就是A x B
的方向。 -
大小 (模): 叉积向量的长度等于两个向量组成的平行四边形的面积。
∣AtimesB∣=∣A∣∣B∣sin(theta)
其中 theta 是向量 A 和 B 之间的夹角。
-
-
应用:
-
计算表面法线: 在 3D 模型中,三角形通常由三个顶点定义。如果我们取三角形的任意两条边(作为向量),它们的叉积就可以得到这个三角形表面的法线向量。这在渲染、碰撞检测和光照中至关重要。
C#
// Unity // 假设 v1, v2, v3 是三角形的三个顶点 Vector3 edge1 = v2 - v1; Vector3 edge2 = v3 - v1; Vector3 normal = Vector3.Cross(edge1, edge2).normalized; // 得到单位法线
-
判断左右关系: 结合点积使用,可以判断一个目标是在你当前方向的左边还是右边。
C#
// Unity Vector3 playerForward = transform.forward; // 玩家正前方 Vector3 toTarget = (target.position - transform.position).normalized; // 指向目标的单位向量 Vector3 crossProductResult = Vector3.Cross(playerForward, toTarget); if (crossProductResult.y > 0) { Debug.Log("目标在玩家的右侧!"); } else if (crossProductResult.y < 0) { Debug.Log("目标在玩家的左侧!"); } else { Debug.Log("目标在玩家的正前方或正后方(或共线)!"); }
(注意:这里
crossProductResult.y
的正负取决于 Unity 的坐标系以及你选择的向量顺序,通常 Y 轴是向上。) -
构建旋转轴: 在一些复杂的旋转操作中,叉积可以帮助我们确定一个旋转的轴线。例如,如果你想让一个物体从
vectorA
旋转到vectorB
,那么它们的叉积A x B
就可以作为旋转轴。
-
3.2.1 重要知识点:叉积与矩阵行列式
叉积的计算公式实际上来源于一个三维行列式。如果你接触过线性代数,可能会知道叉积可以表示为:
KaTeX parse error: Expected 'EOF', got '&' at position 50: …x} \\mathbf{i} &̲ \\mathbf{j} & …
其中 mathbfi,mathbfj,mathbfk 是 x, y, z 轴的单位向量。虽然在实际开发中我们直接使用引擎提供的 API,但了解其与行列式的联系,能帮助你从更抽象的层面理解叉积的几何特性,尤其是在计算机图形学中,行列式在体积、面积、方向性判断等方面扮演着重要角色。
4. 向量在 Unity/UE 中的常见应用实践
理论终归要服务于实践。让我们看看向量在游戏开发中的一些经典应用场景。
4.1 移动与位置控制
-
平移:
C#
// Unity // 沿着世界空间的 X 轴正方向每秒移动 5 个单位 transform.position += Vector3.right * 5f * Time.deltaTime; // 沿着物体自身的“前方”方向每秒移动 3 个单位 transform.position += transform.forward * 3f * Time.deltaTime;
这里
Vector3.right
和transform.forward
都是向量,5f
和3f
是速度标量,Time.deltaTime
是一个时间标量,用于确保移动帧率无关。 -
瞬移与设置位置:
C#
// Unity transform.position = new Vector3(10, 5, 0); // 直接设置到世界坐标的某个点
4.2 计算距离与方向
-
两点之间的距离:
C#
// Unity float distance = Vector3.Distance(player.position, enemy.position); // 或者性能更优的平方距离 float sqrDistance = (player.position - enemy.position).sqrMagnitude;
-
朝向某个目标:
C#
// Unity Vector3 directionToTarget = target.position - transform.position; // 如果只需要方向,不需要长度: Vector3 normalizedDirection = directionToTarget.normalized;
这个
normalizedDirection
就是你让 AI 追逐敌人时,应该朝着的方向向量。
4.3 物理交互与力学
-
施加力:
C#
// Unity (需要 Rigidbody 组件) Rigidbody rb = GetComponent<Rigidbody>(); Vector3 pushDirection = transform.forward; // 沿着物体前方推 float forceMagnitude = 100f; rb.AddForce(pushDirection * forceMagnitude); // 施加一个推力
这里的力也是一个向量,由方向和大小组成。
-
反弹: 当一个物体以某种方向(入射向量)撞击一个表面(由法线向量定义)时,它会沿着反射向量的方向弹开。
C#
// Unity Vector3 incidentVector = // 撞击时的速度方向(通常是负的移动方向) Vector3 surfaceNormal = // 表面法线 // 反射向量 = 入射向量 - 2 * (入射向量与法线的点积) * 法线 Vector3 reflectedVector = Vector3.Reflect(incidentVector, surfaceNormal);
Vector3.Reflect
是 Unity 内置的非常方便的函数,它直接帮你实现了上述的向量计算。这在制作弹球游戏、投掷物反弹等场景中非常实用。
4.4 颜色表示
在 Unity 中,Color
类型实际上也是一种向量表示(Vector4
的一种特殊形式,R, G, B, A
)。你可以对颜色进行加减法、标量乘法等操作,实现颜色的混合或亮度调整。
C#
// Unity
Color red = Color.red;
Color blue = Color.blue;
Color purple = red + blue; // 颜色叠加
Color brighterRed = red * 1.5f; // 增加亮度
这从侧面反映了向量概念在图形学中的广泛应用。
5. 常见问题与面试考点
掌握了向量的基础概念和应用,你就能自信地回答面试中关于向量的问题了。以下是一些常见的面试题及其核心考察点:
-
问:请解释点和向量的区别。在 Unity 中,你用
Vector3
来表示它们时,如何区分其意义?-
考察点: 对点和向量本质区别的理解。
-
解析: 点表示空间中的一个绝对位置,没有方向和大小。向量表示一个方向和大小的量,代表位移或趋势,没有固定起点。在 Unity 中,
Vector3
是一种数据结构,它可以用来存储三维坐标。当Vector3
存储的是transform.position
时,它表示一个点;当它存储的是(target.position - current.position)
的结果时,它表示一个向量(方向和长度)。
-
-
问:在计算两个物体之间的距离时,为什么要优先使用
(pos1 - pos2).sqrMagnitude
而不是Vector3.Distance(pos1, pos2)
?这会带来什么性能优势?-
考察点: 对
magnitude
和sqrMagnitude
性能差异的理解,以及微优化意识。 -
解析:
Vector3.Distance()
内部会进行开方 (Square Root) 运算,而sqrMagnitude
只计算平方和,避免了开方。开方运算是 CPU 密集型操作,当只需要比较距离大小而非精确距离值时(例如判断是否在某个范围内),比较平方距离比比较实际距离效率更高。这在游戏循环中频繁的距离判断场景下,能带来可观的性能提升。
-
-
问:请解释向量“点积”的几何意义和在游戏开发中的一个应用场景。
-
考察点: 对点积核心概念的理解和实际应用能力。
-
解析: 点积衡量两个向量方向的相似程度。当两个向量都是单位向量时,点积结果等于它们夹角的余弦值。
- 应用场景: 视野检测。例如,通过计算玩家的
transform.forward
向量和指向敌人的向量之间的点积,可以判断敌人是否在玩家的视野前方。如果点积结果为正,则在前方;如果点积结果接近 1,则几乎正对着。
- 应用场景: 视野检测。例如,通过计算玩家的
-
-
问:请解释向量“叉积”的几何意义和在游戏开发中的一个应用场景。
-
考察点: 对叉积核心概念的理解和实际应用能力。
-
解析: 叉积得到一个同时垂直于两个输入向量的新向量。其方向遵循右手定则。
- 应用场景: 计算表面法线。在 3D 模型中,一个三角形的两个边向量的叉积可以用来计算该三角形表面的法线方向。这对于光照计算、背面剔除等渲染管线操作至关重要。另一个应用是判断左右关系(如 AI 寻路中判断目标在左还是右)。
-
-
问:当你需要让一个角色沿着输入方向以恒定速度移动时,为什么要将输入向量进行
normalized
操作?-
考察点: 对单位向量作用的理解,以及游戏移动控制中的常见陷阱。
-
解析:
normalized
操作将向量的长度变为 1,只保留方向信息。如果不进行normalized
,当玩家同时按下“W”(前进)和“A”(向左)键时,产生的(x, z)
向量的长度会是 sqrt12+12approx1.414,比只按一个键时的长度 1 要长。这意味着斜向移动时速度会变快,导致不一致的游戏体验。将输入向量归一化后,无论玩家如何组合输入,移动方向向量的长度都保持为 1,乘以固定的速度标量,就能确保恒定速度移动。
-
总结与展望
恭喜你,已经完成了 3D 数学系列教程的第一篇!我们深入探讨了:
-
点与向量的根本区别及其在引擎中的表示。
-
向量的加减法、标量乘除法及其在位移、速度控制中的应用。
-
向量的模与单位向量,以及
sqrMagnitude
这种重要的性能优化技巧。 -
点积与叉积这两个核心运算的几何意义、计算方式和在视野判断、光照、法线计算、左右判断等复杂场景中的应用。
-
硬核知识点如向量投影和叉积与行列式的关系,为你打开更深层理解的大门。
向量是 3D 空间中的“原子”,理解并熟练运用它,是你掌握更复杂 3D 数学概念(如旋转、矩阵变换)的基础。
在下一篇《旋转与欧拉角、四元数(核心变换篇)》中,我们将挑战 3D 数学中另一个令人着迷且充满陷阱的话题:如何优雅地表示和处理物体的旋转。你会遇到臭名昭著的“万向节死锁”,并学习如何使用强大的四元数来规避它。
现在,你对向量的概念和基本应用是否已经有了清晰的认识?如果你在实践中遇到任何具体问题,或者想进一步探讨某个概念,随时可以提问!