1. 矩阵变换在Unity中的核心作用
在Unity开发中,矩阵(Matrix)是描述物体位置、旋转和缩放的核心数学工具。每个GameObject的Transform组件背后,实际上都是由一个4x4的变换矩阵驱动的。这个矩阵包含了物体的全部空间信息:
- 第1-3列的前3行表示物体的旋转和缩放
- 第4列的前3行表示物体的位置
- 最后一行通常是(0,0,0,1)用于齐次坐标计算
当我们直接修改Transform的position、rotation或scale时,Unity底层会自动重新计算这个变换矩阵。但有时候直接操作矩阵反而能实现更灵活的控制,特别是在需要精确计算物体间相对位置时。
关键提示:Unity使用列主序矩阵,与DirectX一致但不同于OpenGL的行主序。这在手动构造矩阵时需要特别注意。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实现物体跟随的三种矩阵方案
2.1 基础跟随:直接矩阵赋值
最简单的跟随实现是将目标物体的变换矩阵直接赋给跟随物体:
csharp复制public Transform target;
void Update() {
transform.localToWorldMatrix = target.localToWorldMatrix;
}
这种方法虽然简单,但存在明显问题:
- 两个物体会完全重合
- 无法控制跟随的偏移量
- 旋转和缩放也会完全一致
2.2 带偏移量的矩阵计算
更实用的方法是通过矩阵乘法计算带有固定偏移的跟随:
csharp复制public Transform target;
public Vector3 offset = new Vector3(0, 1, 0);
void Update() {
Matrix4x4 targetMatrix = target.localToWorldMatrix;
Matrix4x4 offsetMatrix = Matrix4x4.Translate(offset);
transform.localToWorldMatrix = targetMatrix * offsetMatrix;
}
这里的关键点:
- 先获取目标的全局变换矩阵
- 创建一个只包含偏移量的平移矩阵
- 通过矩阵乘法组合两个变换
2.3 分轴控制的跟随矩阵
对于需要不同轴不同跟随策略的场景,可以分解矩阵的各个分量:
csharp复制public Transform target;
public bool followPositionX = true;
public bool followRotationY = true;
void Update() {
Vector3 position = transform.position;
Quaternion rotation = transform.rotation;
if(followPositionX) position.x = target.position.x;
if(followRotationY) {
Vector3 euler = rotation.eulerAngles;
euler.y = target.rotation.eulerAngles.y;
rotation = Quaternion.Euler(euler);
}
transform.SetPositionAndRotation(position, rotation);
}
这种方法虽然不直接操作矩阵,但原理相通,都是对变换分量进行选择性应用。
3. 矩阵跟随的进阶应用
3.1 平滑跟随的插值实现
直接矩阵赋值会导致跟随僵硬,可以通过插值实现平滑过渡:
csharp复制public float smoothTime = 0.3f;
private Vector3 velocity = Vector3.zero;
void Update() {
Matrix4x4 targetMatrix = target.localToWorldMatrix;
Vector3 targetPosition = targetMatrix.GetPosition();
Quaternion targetRotation = targetMatrix.rotation;
transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, smoothTime);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 10f);
}
这里结合了矩阵获取目标变换和插值算法,实现了带缓冲效果的跟随。
3.2 层级关系的矩阵处理
当物体有层级关系时,需要注意矩阵空间转换:
csharp复制// 获取目标在父节点空间中的矩阵
Matrix4x4 localMatrix = target.parent.worldToLocalMatrix * target.localToWorldMatrix;
// 应用到跟随物体
transform.localPosition = localMatrix.GetPosition();
transform.localRotation = localMatrix.rotation;
transform.localScale = localMatrix.lossyScale;
这种处理方式可以保持跟随物体与目标在父节点空间中的相对关系。
3.3 物理模拟中的矩阵应用
在Rigidbody物理系统中直接修改矩阵会与物理引擎冲突,正确做法是:
csharp复制public Rigidbody follower;
public Transform target;
public float force = 10f;
void FixedUpdate() {
Vector3 positionDelta = target.position - follower.position;
follower.AddForce(positionDelta * force);
Quaternion rotationDelta = target.rotation * Quaternion.Inverse(follower.rotation);
rotationDelta.ToAngleAxis(out float angle, out Vector3 axis);
follower.AddTorque(axis * angle * 0.1f);
}
4. 性能优化与常见问题
4.1 矩阵操作的性能考量
频繁的矩阵计算可能带来性能开销,特别是在Update中。优化建议:
- 缓存常用矩阵:
csharp复制private Matrix4x4 cachedMatrix;
void Update() {
if(target.hasChanged) {
cachedMatrix = target.localToWorldMatrix;
target.hasChanged = false;
}
// 使用cachedMatrix...
}
- 避免不必要的矩阵分解:
csharp复制// 不好:需要完整矩阵分解
Vector3 position = matrix.GetPosition();
// 更好:直接访问已知分量
Vector3 position = new Vector3(matrix.m03, matrix.m13, matrix.m23);
4.2 常见问题排查
- 物体朝向错误:
- 检查矩阵是否包含非均匀缩放
- 确认旋转顺序是否正确
- 验证是否意外修改了矩阵的旋转分量
- 跟随位置偏移:
csharp复制// 调试显示矩阵信息
Debug.Log($"Position: {matrix.GetPosition()}\nRotation: {matrix.rotation.eulerAngles}\nScale: {matrix.lossyScale}");
- 缩放异常:
- 避免直接修改含有缩放的矩阵
- 使用Matrix4x4.TRS方法构造新矩阵
4.3 矩阵与四元数的选择
虽然本文聚焦矩阵,但在Unity中实际开发时:
- 简单变换:直接使用Transform组件
- 复杂组合变换:考虑矩阵
- 旋转插值:使用四元数(Quaternion)
- 物理模拟:使用Rigidbody提供的方法
5. 实战案例:第三人称相机跟随
综合应用矩阵知识实现一个完整的相机跟随:
csharp复制public class ThirdPersonCamera : MonoBehaviour {
public Transform target;
public Vector3 offset = new Vector3(0, 2f, -5f);
public float smoothTime = 0.2f;
public float rotationSpeed = 3f;
private Vector3 velocity = Vector3.zero;
private float currentYaw;
private float currentPitch;
void LateUpdate() {
// 获取目标矩阵
Matrix4x4 targetMatrix = target.localToWorldMatrix;
// 计算理想位置
Vector3 desiredPosition = targetMatrix.MultiplyPoint(offset);
// 平滑移动
transform.position = Vector3.SmoothDamp(
transform.position,
desiredPosition,
ref velocity,
smoothTime);
// 旋转控制
currentYaw += Input.GetAxis("Mouse X") * rotationSpeed;
currentPitch -= Input.GetAxis("Mouse Y") * rotationSpeed;
currentPitch = Mathf.Clamp(currentPitch, -30, 70);
// 组合旋转
Quaternion rotation = Quaternion.Euler(currentPitch, currentYaw, 0);
transform.rotation = rotation;
// 确保看向目标
transform.LookAt(targetMatrix.GetPosition() + Vector3.up * 1.5f);
}
}
这个实现结合了:
- 矩阵计算基础偏移位置
- 输入控制的旋转处理
- 平滑插值过渡
- 最终朝向修正
6. 矩阵知识的延伸应用
掌握矩阵操作后,可以在Unity中实现更多高级功能:
- 自定义空间扭曲效果:
csharp复制// 创建波浪形变换矩阵
Matrix4x4 WaveMatrix(float time, float intensity) {
Matrix4x4 matrix = Matrix4x4.identity;
matrix.m01 = Mathf.Sin(time) * intensity;
return matrix;
}
- 程序化动画生成:
csharp复制// 通过矩阵序列实现关键帧动画
IEnumerator MatrixAnimation(Transform target, Matrix4x4[] frames, float duration) {
float step = duration / frames.Length;
for(int i = 0; i < frames.Length; i++) {
target.localToWorldMatrix = frames[i];
yield return new WaitForSeconds(step);
}
}
- 高级着色器效果:
在Shader中,矩阵常用于:
- 顶点变换
- 法线变换
- 纹理坐标变换
- 多Pass效果组合
在Unity开发中深入理解矩阵,不仅能解决物体跟随这类基础需求,还能为更复杂的图形编程和游戏机制实现打下坚实基础。建议从简单案例开始,逐步尝试更复杂的矩阵应用场景。
