Bevy 坐标系统概览

Bevy 与多数游戏引擎类似,采用了多种坐标系统,每种系统都为特定任务量身定制。本文将主要探讨以下几种系统:

  • 世界空间(World Space)
  • 局部空间(Local Space)
  • 全局(实体世界)空间(Global (Entity World) Space)
  • 屏幕/UI 空间(Screen/UI Space)
  • 视口(Viewport)
  • 归一化设备坐标(Normalized Device Coordinates, NDC)。

全局世界坐标系统

right-hand.png

Bevy 为其游戏世界采用的是右手坐标系,Y 轴向上。这是一个基础设定,影响着 3D 几何体和变换的解释方式。开发者可以通过“右手定则”来形象化轴向:通常,右手拇指指向 X 轴正方向,食指指向 Y 轴正方向,中指指向 Z 轴正方向。值得注意的是,此坐标系统在 2D 和 3D 场景中保持一致,这简化了在两者之间切换或共享逻辑的开发过程。

轴向详解

  • +X 轴: 从左指向右。
  • +Y 轴: 从下指向上(即“上方”)。
  • +Z 轴 (2D 上下文): 从远指向近,实际上是“从屏幕内部指向观察者”。这用于 2D 精灵的层叠,Z 值较大的精灵会渲染在 Z 值较小的精灵之上

原点

  • 对于 2D 场景,原点 (0.0, 0.0) 默认位于屏幕中心
  • 对于 3D 场景,原点是抽象游戏世界中的一个点,其在屏幕上的具体位置取决于相机的位置和朝向。

3D 特定约定

  • 尽管 Y 轴始终指向上,但在 3D 环境中,前进方向通常被认为是沿着 -Z 轴。这是诸如 OpenGL 等系统中常见的约定。
  • 这意味着一个位于 (0,0,0) 并面向“前方”的物体,其视线将朝向 (0,0,-1)。
  • Bevy 的坐标系统与 Godot、Maya 和 OpenGL 相同,但与 Unity 相比,特别是在 Z 轴的朝向上有所不同。

实体特定坐标系统:TransformGlobalTransform

实体变换简介

在 Bevy 中,实体在游戏世界中的位置、旋转和缩放通过变换组件进行控制。Bevy 为此使用了两个主要组件:TransformGlobalTransform。代表游戏世界中物体的实体通常需要同时拥有这两个组件。当插入 Transform 组件时,GlobalTransform 会被自动插入。

局部空间 (Transform 组件)

Transform 组件定义了实体相对于其父实体坐标系的位置、旋转和缩放。如果一个实体没有 Parent 组件,则其 Transform 是相对于世界原点的。

  • 字段:
    • translation: Vec3: 实体相对于其父实体的平移。在 2D 中,Z 分量常用于 Z 轴排序。
    • rotation: Quat: 实体相对于其父实体的朝向(旋转),以四元数表示。
    • scale: Vec3: 实体相对于其父实体的缩放。
  • 开发者通常通过修改 Transform 组件来移动、旋转或缩放实体。例如,Transform::from_xyz(5.0, 6.0, 7.0) 会创建一个具有特定局部平移的变换。

世界空间 (GlobalTransform 组件)

GlobalTransform 组件表示实体在世界空间中的绝对位置、旋转和缩放。它是一个从实体局部坐标到世界空间坐标的仿射变换。它由 Bevy 内部管理,并通过逐级应用层级结构中每个祖先实体的 Transform 来计算得出。

GlobalTransform 中的平移、旋转和缩放不能直接作为独立字段访问,因为复杂的层级变换可能包含剪切(shearing),而剪切无法用简单的平移、旋转和缩放来表示。它以一种优化的方式存储(例如使用 Affine3A 结构)。存在一些方法尝试将其分解,如 translation()to_scale_rotation_translation()compute_transform()

变换传播 (Transform Propagation)

TransformGlobalTransform 之间的同步由一个名为“TransformSystem::TransformPropagate”的 Bevy 内部系统处理。该系统在 PostUpdate 调度阶段运行。

一个关键点是:当一个 Transform 被修改时,对应的 GlobalTransform 不会立即更新。它们会保持不同步,直到变换传播系统运行。可以使用 TransformHelper 系统参数在传播之前获取最新的 GlobalTransform,但这需要考虑性能影响 , 因为TransformHelper只是帮你转换最新的GlobalTransform的数据, 并没有更新组件里的数据.后面Bevy会再运算一次.

设计考量与影响

父子关系和局部 Transform 组件使得构建复杂的、有关节的物体(例如角色的肢体、带有炮塔的车辆)变得容易。对父实体变换的修改会自动影响其所有子实体。这种层级系统 是游戏引擎场景图的基础。开发者设置的局部 Transform 与计算得出的世界状态 GlobalTransform 之间的区别是核心。潜在的问题源于 GlobalTransform 的延迟更新。如果在 Update 阶段的一个系统修改了某个 Transform,而同一阶段的另一个系统在该帧的 PostUpdate 之前查询其 GlobalTransform,它将获得过时的数据。如果不理解这一点,可能会导致难以察觉的错误。

虽然开发者主要操作 Transform,但最终决定实体在世界中如何显示和交互的是 GlobalTransform。渲染系统、物理引擎和射线投射通常会使用 GlobalTransformGlobalTransform 是到 世界空间坐_的仿射变换。

GlobalTransform 可能由于层级结构中的剪切而无法直接表示为简单的平移/旋转/缩放,并且以 Affine3A 形式存储 ,这表明了优化和正确性的考量。仿射矩阵 (Affine3A) 可以表示平移、旋转、缩放和剪切的任意组合。如果一个子实体被非均匀缩放,然后其父实体再进行旋转,最终的全局变换可能包含剪切。试图将其强制转换回独立的 Vec3(缩放)和 Quat(旋转)可能会丢失信息。将其存储为矩阵(或仿射变换结构)保留了完整的变换信息,这对于复杂的层级结构是一种更鲁棒的方法。

屏幕、UI 及光标坐标系统

与世界坐标的区别

Bevy 为 UI 元素、屏幕空间效果和光标/鼠标输入使用一个独立的坐标系统,该系统遵循 UI 工具和 Web 开发中常见的约定。

原点

原点 (0.0, 0.0) 位于屏幕或 UI 节点的左上角

轴向

  • +X 轴: 从左指向右,范围从 0.0 (屏幕左边缘) 到屏幕宽度像素数 (屏幕右边缘)。

  • +Y 轴: 向下指向,范围从 0.0 (屏幕上边缘) 到屏幕高度像素数 (屏幕下边缘)。

单位

单位表示逻辑像素,这些像素已针对 DPI (每英寸点数) 缩放进行了补偿。这确保了 UI 元素在不同像素密度的显示器上显示尺寸一致。

用途

  • 主要用于 UI 布局,通常从上到下排列。
  • 光标位置和其他窗口 (屏幕空间) 坐标也遵循此约定。
  • 例如,UiScale 资源可以影响 UI 的缩放。

相机、视口与归一化设备坐标 (NDC)

image.png

Camera 组件的角色

相机是 Bevy 渲染的核心;它们定义了要绘制什么、如何绘制以及在哪里绘制(绘制到哪个渲染目标)。至少需要一个相机实体才能显示任何内容。Camera 组件存储了诸如投影类型(透视/正交)、视场角 (FOV)、裁剪平面、渲染目标和视口等属性。

视口 (Viewport)

Viewport 是渲染目标(例如窗口)内的一个可选矩形区域,相机将内容渲染到该区域。如果未设置,相机将渲染到整个渲染目标。视口坐标通常以物理像素为单位,定义了渲染目标的一个子区域。Camera 组件拥有如 logical_viewport_size()physical_viewport_size() 等方法来获取此视口的尺寸。

归一化设备坐标 (Normalized Device Coordinates, NDC)

NDC 是渲染管线中,在投影变换之后、视口变换之前使用的一种规范化的中间坐标系统。在 NDC 中,可见体积(视锥体)被映射到一个立方体。X 和 Y 轴的坐标通常范围从 -1.0 到 1.0,Z 轴(深度)的坐标范围从 0.0 到 1.0。Bevy 的 Camera 提供了在世界坐标与 NDC 之间转换的方法:world_to_ndc()ndc_to_world()

投影 (Projection)

相机的投影(例如,通过 Camera3dCamera2d 设置的 Projection::PerspectiveProjection::Orthographic)决定了 3D 世界坐标如何映射到 2D 屏幕坐标。透视投影产生真实的深度感,物体越远显得越小 ,FOV 是其关键参数。正交投影则无论物体距离远近都以相同尺寸渲染,适用于 2D 游戏、CAD 或特定艺术风格。

设计考量与影响

NDC 是图形管线中一个关键的、通常“不可见”的标准化空间。所有 3D 几何体,无论其世界坐标或相机投影如何,都会被转换到这个通用的 [-1,1] 立方体(或类似范围)中,然后才被映射到屏幕像素。GPU 的光栅化器在归一化空间中操作。通过将所有内容转换到 NDC ,后续的裁剪(丢弃视图外的几何体)和视口映射(将 NDC 缩放和平移到像素坐标)步骤变得通用,并且独立于原始世界尺度或相机设置。理解 NDC 是掌握 3D 投影基本工作原理的关键。

视口 不仅仅用于定义渲染区域;它对于实现分屏多人游戏、小地图或渲染到纹理的特定区域等技术至关重要。每个 Camera 都可以有自己的视口。这允许多个相机渲染到同一窗口的不同部分。相机视图与整个窗口的解耦为呈现方式提供了显著的灵活性。

Bevy 坐标系统比较

特性世界空间 (2D/3D 场景)实体局部空间 (Transform)实体世界空间 (GlobalTransform)屏幕/UI/光标空间视口空间归一化设备坐标 (NDC)
原点默认:屏幕中心 (2D), 游戏定义 (3D)相对于父实体的原点世界中的绝对位置窗口/UI 节点的左上角渲染目标内视口矩形的左上角投影视图的中心 (X=0, Y=0, Z=0.5)
X-轴指向右相对于父实体的 X-轴世界中指向右指向右指向右指向右 (-1 到 1)
Y-轴指向上相对于父实体的 Y-轴世界中指向上指向下指向下指向上 (-1 到 1)
Z-轴指向观察者 (2D), -Z 常为前进方向 (3D)相对于父实体的 Z-轴世界中指向观察者/前进方向N/A (或用于分层)(深度, 视上下文而定)“出”屏幕/深度 (0 到 1)
手性右手(继承/定义局部框架)右手(通常为 2D, N/A)(通常为 2D, N/A)左手 (通常在投影后)
单位游戏定义的逻辑单位游戏定义的逻辑单位游戏定义的逻辑单位逻辑像素物理像素归一化 (-1 到 1 或 0 到 1)
主要用途定位所有游戏元素, 全局参考定义实体相对于其父实体的姿态实体的最终世界姿态,用于渲染/物理UI 布局, 鼠标输入定义渲染目标的子区域渲染管线的中间步骤, 裁剪

坐标系统间的转换

Camera 组件提供了在不同空间之间转换坐标的主要 API,特别是涉及世界、视口/屏幕和 NDC 的转换。下表总结了 bevy::render::camera::Camera 中核心的坐标转换 API:

方法输入参数输出主要用例
world_to_viewport(…)&GlobalTransform (相机), Vec3 (世界坐标)Result<Vec2, ViewportConversionError>获取 3D 世界点对应的 2D 屏幕像素坐标。
viewport_to_world(…)&GlobalTransform (相机), Vec2 (视口坐标)Result<Ray3d, ViewportConversionError>从相机通过屏幕点向 3D 世界投射一条射线 (用于 3D拾取)。
viewport_to_world_2d(…)&GlobalTransform (相机), Vec2 (视口坐标)Result<Vec2, ViewportConversionError>从屏幕点获取 2D 世界坐标 (用于 2D 游戏/正交相机)。
world_to_ndc(…)&GlobalTransform (相机), Vec3 (世界坐标)Option将世界坐标转换为归一化设备坐标。
ndc_to_world(…)&GlobalTransform (相机), Vec3 (NDC 坐标)Option将归一化设备坐标转换回世界坐标。
logical_viewport_size()&selfOption获取相机视口的逻辑尺寸。
physical_viewport_size()&selfOption获取相机视口的物理像素尺寸。