Unity3D数学第三篇:坐标系与变换矩阵(空间转换篇)

发布于:2025-08-02 ⋅ 阅读:(12) ⋅ 点赞:(0)

Unity3D数学第一篇:向量与点、线、面(基础篇)

Unity3D数学第二篇:旋转与欧拉角、四元数(核心变换篇)

Unity3D数学第三篇:坐标系与变换矩阵(空间转换篇)

Unity3D数学第四篇:射线与碰撞检测(交互基础篇)

Unity3D数学第五篇:几何计算与常用算法(实用算法篇)

第三篇:坐标系与变换矩阵(空间转换篇)

在 3D 游戏开发中,我们不断地在不同的“参考系”或“视角”之间切换。一个物体的“前方”可能相对于它自身,也可能相对于整个世界;鼠标点击的屏幕位置,最终要对应到 3D 世界中的某个点。这些转换的背后,离不开坐标系 (Coordinate Systems)变换矩阵 (Transformation Matrices) 的强大作用。

本篇教程将深入探讨 Unity 中各种常见的坐标系,揭示它们之间的转换机制,并为你揭开变换矩阵这一“幕后英雄”的面纱。理解这些概念,将让你对 3D 空间中的一切变换拥有更深刻的洞察力。


1. 深入理解坐标系:3D 空间的“视角”

想象一个房间,你可以在房间的中心描述一件家具的位置(例如,“它在我前面两米”),也可以相对于房间的墙角来描述(例如,“它在东墙角往北三米,往西两米”)。这两种描述方式,分别对应着不同的坐标系。

在 3D 游戏世界中,存在多种相互关联的坐标系,它们服务于不同的目的。

1.1 局部坐标系 (Local Space / Object Space)

  • 概念: 每个独立的 3D 物体都有其自身的局部坐标系。这个坐标系的原点通常位于物体的中心点(或称为轴心点 Pivot),而它的 X、Y、Z 轴则沿着物体自身的特定方向。

    • 例如,一个汽车模型,它的局部 Y 轴通常指向它的“上方”(车顶),局部 Z 轴指向它的“前方”(车头),局部 X 轴指向它的“右方”。
  • 特性: 当物体自身旋转或移动时,它的局部坐标系也跟着它一起旋转和移动。因此,一个点在物体局部坐标系中的坐标是固定不变的

  • Unity 中的体现:

    • Transform.localPosition:表示物体相对于其父对象的局部位置。如果物体没有父对象,则等同于 transform.position

    • Transform.localRotation:表示物体相对于其父对象的局部旋转。

    • Transform.localScale:表示物体相对于其父对象的局部缩放。

    • transform.forwardtransform.uptransform.right:这些便捷属性返回的是物体在世界坐标系下其局部 Z、Y、X 轴的方向向量。例如,transform.forward 就是物体当前面向的世界方向

1.2 世界坐标系 (World Space)

  • 概念: 整个 3D 场景的全局、固定参考系。它的原点通常位于 (0, 0, 0),X、Y、Z 轴指向固定方向。在 Unity 中,通常:

    • X 轴: 指向右方 (Red Axis)

    • Y 轴: 指向上方 (Green Axis)

    • Z 轴: 指向前方 (Blue Axis)

  • 特性: 世界坐标系是所有物体共享的“大地图”。所有物体最终都会被放置在世界坐标系中的某个位置和姿态。

  • Unity 中的体现:

    • Transform.position:表示物体在世界坐标系中的位置。

    • Transform.rotation:表示物体在世界坐标系中的旋转。

    • Transform.lossyScale:表示物体在世界坐标系中的最终缩放(包含了父级的缩放影响)。

1.3 父子关系与层级变换

在 Unity 中,游戏对象可以有父子关系。当一个对象成为另一个对象的子对象时,它的局部坐标系就变成了相对于其父对象的坐标系。子对象的任何变换都会受到父对象变换的影响。

  • 示例: 如果一个手臂是身体的子对象,当身体旋转时,手臂也会跟着旋转(因为手臂的局部坐标系随着身体的世界坐标系一起旋转了)。而手臂自身的局部旋转,则是相对于它当前所处的“父级空间”进行的。

  • Transform.SetParent() 方法可以用来建立和解除父子关系,这在运行时动态组合对象时非常有用,例如拾取武器或附件。


为什么要区分局部坐标系和世界坐标系?

理解这些坐标系的关键在于它们处理问题的方式:

  • 局部坐标系方便描述物体自身的属性(例如,我向前走,就是沿着我的局部 Z 轴),因为它不受外部环境变化的影响。

  • 世界坐标系方便描述物体在整个场景中的绝对位置和关系(例如,哪个物体离世界原点最近),因为它是一个统一的参考标准。

在游戏开发中,我们经常需要在两者之间进行转换。

1.4 屏幕坐标系 (Screen Space)

  • 概念: 你的游戏屏幕(或窗口)上的 2D 像素坐标系。

    • 原点通常在屏幕的左下角 (0, 0)

    • X 轴向右延伸,Y 轴向上延伸。

    • Z 轴通常表示深度,即点离摄像机的距离。

  • 特性: 像素单位,直接对应屏幕上的视觉呈现。

  • Unity 中的体现:

    • Input.mousePosition:鼠标在屏幕上的像素坐标。

    • Input.GetTouch(0).position:触控点在屏幕上的像素坐标。

    • UI Canvas 的 Screen Space - Overlay 模式下的 UI 元素位置。

1.5 视口坐标系 (Viewport Space)

  • 概念: 相机在渲染时看到的**“视口”**的 2D 归一化坐标系。

    • 原点在视口的左下角 (0, 0)

    • 右上角是 (1, 1)

    • X、Y 值范围是 0.01.0

    • Z 轴同样表示深度,即点离摄像机的距离。

  • 特性: 独立于屏幕分辨率。无论屏幕多大,视口左下角始终是 (0,0),右上角始终是 (1,1)。这对于判断物体是否在屏幕内非常有用。

  • Unity 中的体现:

    • Camera.WorldToViewportPoint():世界坐标转视口坐标。

    • Camera.ViewportToWorldPoint():视口坐标转世界坐标。

    • Camera.ViewportToScreenPoint():视口坐标转屏幕坐标。

1.6 其他重要但无需深究的坐标系

  • 摄像机空间 (Camera Space / View Space): 以摄像机自身为原点和轴向的 3D 坐标系。所有物体在渲染前都会被转换到这个空间。

  • 裁剪空间 (Clip Space): 在摄像机空间之后,点被投影到一个立方体(归一化设备坐标,NDC)中。这个空间用于剔除视锥体外的点。

  • 归一化设备坐标 (Normalized Device Coordinates - NDC): 经过裁剪空间后,所有可见点被映射到一个标准的立方体中,X, Y, Z 轴范围都是 [-1, 1]

这些更深层次的坐标系主要在图形渲染管线内部使用,作为开发者,通常只需要理解它们的存在和作用流程,而无需手动操作它们。了解它们能让你更好地理解 3D 世界最终如何映射到 2D 屏幕上。


2. 坐标系转换:在不同“视角”之间穿梭

在游戏开发中,我们经常需要在不同坐标系之间进行点的转换,例如将一个世界坐标的点转换为局部坐标,或者将鼠标的屏幕坐标转换为 3D 世界中的点击点。Unity 提供了方便的 API 来实现这些转换。

2.1 局部坐标系与世界坐标系之间的转换

这些方法都可以在任何 Transform 组件上调用。

  • Transform.TransformPoint(Vector3 position):局部点转世界点

    • 将一个在当前 Transform局部坐标系中定义的点 (position) 转换到世界坐标系中。

    • 应用: 计算角色前方 N 米处的世界坐标、计算子弹从枪口(作为局部点)射出的世界起始位置。

    C#

    // Unity
    // 假设你在物体 A 上,想知道物体 A 的局部坐标 (0, 0, 5) 在世界中的位置
    Vector3 localPoint = new Vector3(0, 0, 5); // 物体 A 前方 5 米处
    Vector3 worldPoint = transform.TransformPoint(localPoint);
    Debug.Log($"局部点 {localPoint} 在世界中是 {worldPoint}");
    
    
  • Transform.InverseTransformPoint(Vector3 position):世界点转局部点

    • 将一个在世界坐标系中定义的点 (position) 转换到当前 Transform局部坐标系中。

    • 应用: 判断一个世界中的点相对于物体的具体方位(例如,敌人相对于我在哪里)、计算局部碰撞点。

    C#

    // Unity
    // 假设你想知道世界坐标 (10, 0, 0) 相对于当前物体的位置
    Vector3 worldPoint = new Vector3(10, 0, 0);
    Vector3 localPoint = transform.InverseTransformPoint(worldPoint);
    Debug.Log($"世界点 {worldPoint} 相对于我是在 {localPoint}");
    
    
  • Transform.TransformDirection(Vector3 direction):局部方向转世界方向

    • 将一个在当前 Transform局部坐标系中定义的方向向量 (direction) 转换到世界坐标系中。

    • 重要: 这个方法只处理旋转,不考虑平移和缩放。它将局部方向向量的方向转换到世界空间,但不会改变其长度。

    • 应用: 获取物体当前的世界前方 (transform.TransformDirection(Vector3.forward) 等同于 transform.forward),获取物体自身某个方向在世界中的表示。

    C#

    // Unity
    // 获取当前物体局部右方在世界中的方向
    Vector3 localRight = Vector3.right;
    Vector3 worldRight = transform.TransformDirection(localRight);
    Debug.Log($"我的局部右方在世界中是 {worldRight}");
    
    
  • Transform.InverseTransformDirection(Vector3 direction):世界方向转局部方向

    • 将一个在世界坐标系中定义的向量 (direction) 转换到当前 Transform局部坐标系中。

    • 同样,只处理旋转,不考虑平移和缩放

    • 应用: 判断世界中的某个力或方向向量对于物体自身来说是哪个方向(例如,世界重力对于斜坡上的角色来说是哪个局部方向)。

    C#

    // Unity
    // 假设你想知道世界坐标的 Vector3.up (世界向上) 对于当前物体是哪个局部方向
    Vector3 worldUp = Vector3.up;
    Vector3 localUp = transform.InverseTransformDirection(worldUp);
    Debug.Log($"世界向上对于我来说是局部 {localUp}");
    
    
  • Transform.TransformVector(Vector3 vector)Transform.InverseTransformVector(Vector3 vector)

    • 这两个方法与 TransformDirection 类似,但它们会考虑缩放。当处理法线(需要逆转置矩阵)或非单位长度的向量时,需要特别注意。在大多数情况下,处理方向用 TransformDirection 即可,除非你明确需要考虑缩放对向量的影响。

2.2 世界坐标系与屏幕/视口坐标系之间的转换

这些方法通常在 Camera 组件上调用。

  • Camera.WorldToScreenPoint(Vector3 position):世界点转屏幕点

    • 将 3D 世界坐标系中的一个点 (position) 转换到 2D 屏幕坐标系中。返回的 Vector3xy 是像素坐标,z 是该点距离摄像机的深度。

    • 应用:

      • 将 3D 物体在屏幕上方的位置显示一个血条 UI。

      • 判断一个 3D 物体是否在屏幕内(检查 x, y 是否在 0Screen.width/Screen.height 之间,且 z > 0)。

    C#

    // Unity
    public Transform target3DObject;
    void Update() {
        Vector3 screenPos = Camera.main.WorldToScreenPoint(target3DObject.position);
        Debug.Log($"3D 物体 {target3DObject.name} 在屏幕上的位置是 {screenPos}");
    
        // 判断是否在屏幕内:
        if (screenPos.z > 0 && screenPos.x >= 0 && screenPos.x <= Screen.width && screenPos.y >= 0 && screenPos.y <= Screen.height) {
            Debug.Log("物体在屏幕内!");
        } else {
            Debug.Log("物体在屏幕外!");
        }
    }
    
    
  • Camera.ScreenToWorldPoint(Vector3 position):屏幕点转世界点

    • 将 2D 屏幕坐标系中的一个点 (position) 转换到 3D 世界坐标系中。

    • 关键: 由于 2D 屏幕点缺少深度信息,你必须手动为 position.z 赋值,才能确定它在 3D 空间中的深度。

    • 应用:

      • 点击屏幕获取 3D 世界中的一个点(例如,点击地面进行移动)。

      • 将 UI 元素的位置映射到 3D 空间中的特定深度。

    C#

    // Unity
    // 鼠标点击屏幕,获取点击处在世界中的位置
    void Update() {
        if (Input.GetMouseButtonDown(0)) {
            Vector3 mouseScreenPos = Input.mousePosition;
            // 必须提供 Z 深度信息!这里假设我们想在相机前方 10 米处获取世界点
            mouseScreenPos.z = 10f; // 这里的 z 是指离相机近裁剪面的距离
            Vector3 worldClickPoint = Camera.main.ScreenToWorldPoint(mouseScreenPos);
            Debug.Log($"鼠标点击屏幕 {Input.mousePosition} 对应世界点 {worldClickPoint}");
        }
    }
    
    

    注意: 如果你需要精确点击 3D 物体表面,通常会结合射线检测 (Raycasting) 来获取深度,这比固定 z 值更精确。我们会在第四篇详细讲解。

  • Camera.WorldToViewportPoint(Vector3 position):世界点转视口点

    • 将 3D 世界坐标系中的一个点 (position) 转换到 2D 视口坐标系中。返回的 Vector3xy 范围是 0.01.0z 是深度。

    • 应用:

      • 判断物体是否在屏幕内(通常比 WorldToScreenPoint 更方便,因为它不依赖 Screen.width/height)。

      • 创建位于屏幕相对位置的 UI 元素(例如,血条总在敌人上方,但不能超出屏幕)。

    C#

    // Unity
    public Transform targetObject;
    void Update() {
        Vector3 viewportPos = Camera.main.WorldToViewportPoint(targetObject.position);
    
        if (viewportPos.z > 0 && viewportPos.x >= 0 && viewportPos.x <= 1 && viewportPos.y >= 0 && viewportPos.y <= 1) {
            Debug.Log("物体在视口内!");
        } else {
            Debug.Log("物体在视口外!");
        }
    }
    
    
  • Camera.ViewportToWorldPoint(Vector3 position):视口点转世界点

    • 将 2D 视口坐标系中的一个点 (position) 转换到 3D 世界坐标系中。

    • 同样需要手动提供 position.z 深度。

    • 应用: 将一个位于屏幕中央(0.5, 0.5)的 UI 元素映射到 3D 世界中的某个深度。


3. 变换矩阵:空间转换的幕后英雄

在所有的坐标系转换背后,真正执行“数学魔法”的是变换矩阵 (Transformation Matrices)。虽然 Unity 为我们封装了大多数底层矩阵操作,但理解它们的存在和作用原理,能让你更深刻地理解 3D 图形学,并在遇到复杂问题时游刃有余。

3.1 什么是变换矩阵?

  • 概念: 在 3D 图形中,一个矩阵 (Matrix) 是一个矩形排列的数字集合。一个 4x4 的矩阵可以同时表示 3D 空间中的平移 (Translation)旋转 (Rotation)缩放 (Scaling) 这三种基本变换。

  • 矩阵乘法: 当一个 3D 点(表示为齐次坐标下的向量)与一个变换矩阵相乘时,就可以得到变换后的新点。

    • 例如,NewPoint = Matrix * OldPoint
  • 关键特性:不满足交换律! MatrixA * MatrixB 不等于 MatrixB * MatrixA。这意味着变换的顺序至关重要。先旋转再平移,和先平移再旋转,会得到完全不同的结果。这解释了为什么 Unity 中 Transform 组件的 positionrotation 操作是分开的,以及为什么父子变换的顺序会影响最终结果。

3.2 矩阵如何工作?(简要原理)

一个 4x4 的变换矩阵通常结构如下:

| Sx  0   0   Tx |
| 0   Sy  0   Ty |
| 0   0   Sz  Tz |
| 0   0   0   1  |

  • 其中 Sx, Sy, Sz 控制缩放

  • 矩阵左上角的 3x3 部分(包含 0 和对角线上的值)控制旋转

  • Tx, Ty, Tz 控制平移

当一个 3D 点 (x, y, z, 1)(这里的 1 是齐次坐标的约定)与这个矩阵相乘时,就能同时实现平移、旋转和缩放。

3.3 MVP 矩阵:3D 世界到 2D 屏幕的渲染管线

在 3D 渲染管线中,一个 3D 模型从其自身定义到最终呈现在屏幕上,会经历一系列的坐标系转换,这些转换都由特定的变换矩阵完成。这就是著名的 MVP 矩阵

  1. 模型矩阵 (Model Matrix / World Matrix):局部空间 -> 世界空间

    • 作用: 将 3D 模型在它自己的局部坐标系中定义的顶点,转换到世界坐标系中。

    • 每个物体都有一个模型矩阵,它由该物体的 Transform.positionTransform.rotationTransform.localScale 决定。

    • 例如,一个角色模型,它的模型矩阵决定了它在世界中的位置、方向和大小

  2. 视图矩阵 (View Matrix):世界空间 -> 摄像机空间

    • 作用:世界坐标系中的所有点,转换到摄像机(或观察者)的局部坐标系中。这相当于从摄像机的角度“看”世界。

    • 视图矩阵实际上是摄像机自身变换矩阵的逆矩阵

    • 它模拟了摄像机的位置和朝向

  3. 投影矩阵 (Projection Matrix):摄像机空间 -> 裁剪空间/NDC 空间

    • 作用:摄像机空间中的 3D 点,投影到 2D 屏幕上,并进行深度挤压和透视变换

    • 它定义了摄像机的视野 (Field of View - FOV)近裁剪面 (Near Clip Plane)远裁剪面 (Far Clip Plane)

    • 两种主要类型:

      • 透视投影 (Perspective Projection): 模拟人眼看世界的效果,远的物体小,近的物体大,有透视感。

      • 正交投影 (Orthographic Projection): 没有透视感,所有物体大小不变,常用于 2D 游戏、工程视图或特殊的 3D 效果。

    • 投影矩阵的输出通常是裁剪空间,然后通过除以 W 分量(透视除法)进入归一化设备坐标 (NDC) 空间,X、Y、Z 轴范围都是 [-1, 1]

渲染管线中的流程:

顶点数据 (局部坐标) -> 模型矩阵 -> 世界坐标 -> 视图矩阵 -> 摄像机空间 -> 投影矩阵 -> 裁剪空间 -> 透视除法 -> NDC 空间 -> 视口变换 -> 屏幕坐标 -> 最终渲染


为什么要理解 MVP 矩阵?

虽然 Unity 帮你处理了所有这些复杂的矩阵乘法,但了解 MVP 矩阵的工作原理,能让你:

  • 理解 3D 渲染的底层逻辑: 当你在 Shader 中处理顶点或像素时,你是在哪个坐标系下操作?这直接影响你的计算结果。

  • 调试渲染问题: 当物体显示不正确时,可能是哪个矩阵出了问题?

  • 实现自定义渲染效果: 如果你需要编写自定义的渲染管道或着色器,对 MVP 矩阵的理解是必不可少的。


4. 坐标系与变换矩阵在 Unity 中的高级应用

理解坐标系和矩阵的工作方式后,我们可以解决一些实际的游戏开发问题。

4.1 鼠标点击精确拾取 3D 物体(结合射线)

这是最常见的应用之一。鼠标点击的 2D 屏幕点,如何找到它在 3D 世界中击中的物体?

C#

// Unity
void Update() {
    if (Input.GetMouseButtonDown(0)) {
        // 1. 将鼠标的屏幕坐标转换为一条射线
        // Camera.main.ScreenPointToRay() 会根据相机位置和方向,从屏幕点发射一条射线
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        // 2. 声明一个 RaycastHit 结构体来存储射线检测结果
        RaycastHit hit;

        // 3. 执行射线检测
        // Physics.Raycast 会沿着射线方向检测碰撞体
        if (Physics.Raycast(ray, out hit, 100f)) { // 100f 是射线的最大检测距离
            Debug.Log($"鼠标点击了物体: {hit.collider.gameObject.name},在世界坐标: {hit.point}");
            // 你可以对 hit.collider.gameObject 进行操作,例如选中、高亮
        } else {
            Debug.Log("没有点击到任何 3D 物体。");
        }
    }
}

这里的核心就是将 2D 的屏幕坐标,通过摄像机的投影信息反推回 3D 世界中的一条射线

4.2 UI 元素跟随 3D 物体(血条、名称板)

让一个 UI 元素(例如角色的血条或名称板)始终位于其对应的 3D 角色上方。

C#

// Unity
public Transform target3DCharacter; // 要跟随的 3D 角色
public RectTransform uiElementRectTransform; // 要跟随的 UI 元素的 RectTransform

void Update() {
    if (target3DCharacter == null || uiElementRectTransform == null || Camera.main == null) return;

    // 1. 将 3D 角色头顶的世界坐标转换为屏幕坐标
    // 假设血条在角色头顶上方 2 个单位处
    Vector3 worldPositionAboveCharacter = target3DCharacter.position + Vector3.up * 2f;
    Vector3 screenPosition = Camera.main.WorldToScreenPoint(worldPositionAboveCharacter);

    // 2. 判断物体是否在摄像机前方且在屏幕内(防止血条显示在屏幕外或背后)
    if (screenPosition.z > 0 &&
        screenPosition.x >= 0 && screenPosition.x <= Screen.width &&
        screenPosition.y >= 0 && screenPosition.y <= Screen.height) {

        uiElementRectTransform.gameObject.SetActive(true); // 显示 UI 元素
        // 3. 将屏幕坐标转换为 UI Canvas 的 RectTransform 坐标
        // UI 坐标通常是相对于 Canvas 的,需要特殊处理
        Vector2 localUIPos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            uiElementRectTransform.parent as RectTransform, // UI 元素的父级 Canvas RectTransform
            screenPosition,
            Camera.main.WorldToScreenPoint(Camera.main.transform.position).z, // 假设 Z 深度相同
            out localUIPos
        );
        // 如果 Canvas 是 Screen Space - Camera 模式,第三个参数是 Camera.main
        // 如果 Canvas 是 Screen Space - Overlay 模式,第三个参数是 null
        // 鉴于目前是 Screen Space - Overlay,直接使用 screenPosition 即可
        // 或者更简单的,直接赋值,Unity UI 系统会处理好:
        uiElementRectTransform.position = screenPosition;

    } else {
        uiElementRectTransform.gameObject.SetActive(false); // 隐藏 UI 元素
    }
}

注意: 对于 Unity UI,RectTransformUtility.ScreenPointToLocalPointInRectangle 通常用于将屏幕点转换为 Canvas 内部的局部坐标,这取决于你的 Canvas 渲染模式(Screen Space - OverlayScreen Space - CameraWorld Space)。这里为了简化,如果 Canvas 是 Screen Space - Overlay 模式,直接将 uiElementRectTransform.position = screenPosition; 即可,Unity 会处理其与屏幕坐标的映射。

4.3 相机边界限制

限制相机只能在特定区域内移动,或者当玩家靠近屏幕边缘时,相机自动平移。

C#

// Unity
public Transform player;
public float edgeOffset = 0.1f; // 屏幕边缘触发区域 (0-1 范围)
public float cameraMoveSpeed = 5f;

void Update() {
    // 1. 将玩家位置转换为视口坐标
    Vector3 viewportPos = Camera.main.WorldToViewportPoint(player.position);

    Vector3 cameraMoveDirection = Vector3.zero;

    // 2. 检查玩家是否接近视口边缘
    if (viewportPos.x < edgeOffset) {
        cameraMoveDirection += Vector3.left;
    } else if (viewportPos.x > 1f - edgeOffset) {
        cameraMoveDirection += Vector3.right;
    }

    if (viewportPos.y < edgeOffset) {
        cameraMoveDirection += Vector3.down;
    } else if (viewportPos.y > 1f - edgeOffset) {
        cameraMoveDirection += Vector3.up;
    }

    // 3. 移动相机
    Camera.main.transform.position += cameraMoveDirection.normalized * cameraMoveSpeed * Time.deltaTime;
}

这里利用视口坐标系的归一化特性,方便地判断玩家相对于屏幕边缘的位置。


5. 常见面试题与深入思考

5.1 面试问答

  1. 问:请解释局部坐标系和世界坐标系的区别。在 Unity 中,当物体存在父子关系时,transform.positiontransform.localPosition 有何不同?

    • 解析:

      • 局部坐标系: 物体自身的坐标系,原点在物体轴心,轴向与物体自身方向一致。描述物体自身的几何数据。

      • 世界坐标系: 整个场景的全局、固定坐标系,原点在 (0,0,0),轴向固定。描述物体在整个世界中的绝对位置。

      • transform.position 表示物体在世界坐标系中的绝对位置。

      • transform.localPosition 表示物体在父对象局部坐标系中的相对位置。如果物体没有父对象,则 localPosition 等同于 position。当父对象移动或旋转时,子对象的 localPosition 不变,但其 position 会随父对象而改变。

  2. 问:如何在 Unity 中将一个世界坐标的点转换到某个物体的局部坐标系?请举例说明应用场景。

    • 考察点: InverseTransformPoint 的使用及其应用。

    • 解析: 使用 transform.InverseTransformPoint(worldPoint) 方法。

      • 应用场景:

        • 判断一个世界中的目标点相对于当前物体的方位。例如,一个 AI 想要知道玩家在它自身的左侧还是右侧、前方还是后方,就需要将玩家的世界位置转换为 AI 的局部坐标,然后判断 X、Z 分量的正负。

        • 获取一个物体被击中时,局部碰撞点在哪里,这在制作特效(例如粒子效果从特定部位发出)或计算局部伤害时很有用。

  3. 问:请简要描述 MVP 矩阵在 3D 渲染管线中的作用。它们分别负责什么转换?

    • 考察点: 对 3D 图形渲染基础流程的理解,矩阵的抽象作用。

    • 解析:

      • MVP 矩阵是 3D 世界中的点最终如何被渲染到 2D 屏幕上的关键。

      • 模型矩阵 (Model Matrix): 将模型自身的局部坐标转换为世界坐标。它决定了物体在世界中的位置、旋转和缩放

      • 视图矩阵 (View Matrix):世界坐标转换为摄像机(观察者)的局部坐标。它模拟了摄像机的位置和朝向

      • 投影矩阵 (Projection Matrix):摄像机空间中的 3D 点投影到 2D 屏幕(归一化设备坐标),并进行透视变换和裁剪。它定义了摄像机的视野 (FOV)、近远裁剪面,决定了最终画面的透视感或正交感。

    • 总结: 模型矩阵定位物体;视图矩阵选择观察视角;投影矩阵定义如何将 3D 世界映射到 2D 屏幕。

  4. 问:如何在 Unity 中让一个 UI 元素(例如血条)始终跟随一个 3D 角色显示在它的头顶?你会用到哪些坐标系转换?

    • 考察点: 综合运用 WorldToScreenPoint 和 UI 坐标系转换的理解。

    • 解析:

      1. 首先,获取 3D 角色头顶的世界坐标

      2. 使用 Camera.main.WorldToScreenPoint() 将这个世界坐标转换为屏幕坐标(像素坐标)。

      3. 然后,将这个屏幕坐标赋值给 UI 元素的 RectTransform.position

      4. 额外考虑: 需要判断 WorldToScreenPoint 返回的 z 值是否大于 0(在摄像机前方),以及 x, y 是否在屏幕范围内,以决定是否显示 UI 元素,避免显示在屏幕外或角色背后。对于不同 Canvas 渲染模式下的 UI 定位,可能还需要额外的 RectTransformUtility 转换。


总结与展望

本篇教程深入探讨了 3D 游戏开发中至关重要的坐标系变换矩阵

  • 我们详细区分了局部坐标系、世界坐标系、屏幕坐标系和视口坐标系,理解了它们各自的用途。

  • 掌握了 Unity 中局部与世界世界与屏幕/视口之间进行转换的各种 API,让你能够灵活处理位置和方向的转换。

  • 揭示了变换矩阵的底层原理,包括其表示平移、旋转、缩放的能力,以及不满足交换律的重要性。

  • 深入理解了 MVP 矩阵 (模型、视图、投影矩阵) 在 3D 渲染管线中的核心作用,让你对 3D 画面如何呈现在屏幕上有了更清晰的认识。

对坐标系和变换的理解,是你在 Unity 中构建复杂层级结构、实现相机逻辑、处理 UI 交互以及深入图形渲染的基石。

在下一篇《射线与碰撞检测(交互基础篇)》中,我们将结合你已经掌握的向量、旋转、坐标系知识,深入学习如何在 3D 世界中进行精确的交互检测,例如鼠标点击拾取、子弹命中判断以及各种物理碰撞。

现在,你对坐标系和变换矩阵的概念是否已经更加清晰了呢?在 Unity 开发中,你是否曾遇到过因坐标系混淆而导致的问题?

Unity3D数学第一篇:向量与点、线、面(基础篇)

Unity3D数学第二篇:旋转与欧拉角、四元数(核心变换篇)

Unity3D数学第三篇:坐标系与变换矩阵(空间转换篇)

Unity3D数学第四篇:射线与碰撞检测(交互基础篇)

Unity3D数学第五篇:几何计算与常用算法(实用算法篇)