Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

设置 XR

Godot 的 XR 系统简介

Godot 引擎内置了一套模块化扩展现实(XR)系统,该系统通过抽象化不同 XR 平台的底层实现细节,以简化跨平台 XR 应用的开发流程。该系统的核心是 XRServer 类,它作为整个 XR 架构的中枢接口,允许开发者通过该接口发现并连接各类 XR 运行时环境。

每一个受支持的 XR 平台,都是通过一个 XRInterface (XR 接口)来实现的。你可以在 here 的功能列表页面找到所有受支持的平台清单。这些受支持的接口会将自己注册到 XRServer (XR 服务器)中,你可以通过调用 XRServer 上的 find_interface 方法来查找它们。当你找到了想要的接口后,就可以通过在该接口上调用 initialize 方法来进行初始化。

警告

已注册的接口仅意味着该接口可用,如果主机系统不支持接口,初始化可能会失败并返回 false。不幸的是,初始化失败的原因在不同平台上各不相同:可能是因为用户没有安装所需软件,或者是根本没插入头戴设备。作为开发者,你必须对接口初始化失败的情况准备好应对措施。

由于 XR 对输出有特殊要求——尤其是头戴设备要为双眼提供不同的图像输出,Godot 中的 XRServer 将覆盖渲染系统的大部分功能。对于独立设备来说,最终输出由 XRInterface 处理,而 Godot 的常规输出系统将被禁用。对于作为第二屏幕使用的桌面 XR 设备来说,可以使用独立的 Viewport 来处理 XR 输出,从而使 Godot 的主窗口能显示其他内容。

备注

请注意,只能有一个主接口负责处理输出到 XR 设备,默认情况下它将是第一个被初始化的接口。 Godot 目前仅支持单个头戴设备的实现。不过,也有可能存在第二个接口(比如为仅支持 3DOF 的设备添加跟踪功能),但这种情况已经愈发少见。

在几乎所有的 XR 应用中,你都会发现三种特定于 XR 的节点类型:

  • XROrigin3D 在所有意图和目的上都代表了游戏空间的中心点。这是一个过于简单的说法,但我们稍后会更详细地解释。XR 平台在物理空间中跟踪的所有对象都相对于此点定位。

  • XRCamera3D 代表着在为 XR 设备渲染输出时使用的(立体)摄像机。该节点的定位由 XR 系统控制,并基于 XR 平台提供的跟踪信息自动更新。

  • XRController3D 代表玩家使用的控制器,通常有两个:左右手各一个。该节点提供对控制器上各种状态的访问,并在玩家按下按钮时发送信号。节点的定位由 XR 系统控制,并基于 XR 平台提供的跟踪信息自动更新。

还有其他与 XR 相关的节点,并且关于这三个节点还有更多内容可以讨论,具体将稍后再谈。

使用哪些渲染器

Godot 为项目提供了 3 种渲染器:兼容(Compatibility)、移动(Mobile)和 Forward+。目前的建议是,对于任何桌面 VR 项目使用移动渲染器,对于在独立头显(如 Meta Quest 3)上运行的项目使用兼容渲染器。XR 项目可以使用 Forward+ 渲染器运行,但与另外两种渲染器相比,目前它针对 XR 的优化还不够完善。

OpenXR

OpenXR 是一项全新的行业标准,它允许不同的 XR 平台通过一套标准化的 API(应用程序接口),向 XR 应用展示自己。这项标准由 Khronos 集团维护,属于开放标准,因此与 Godot 的理念(利益)非常契合。

OpenXR 的 Vulkan 实现与 Vulkan 紧密集成,并接管了 Vulkan 系统的一部分。这就要求在设置 XR 系统之前,需要对 Vulkan 渲染器中的某些核心图形功能先行集成。这是将 OpenXR 包含为核心接口的主要决定因素之一。

这也意味着需要在 Godot 启动时启用 OpenXR,以便正确设置。请检查项目设置中 XR > OpenXR 下的 Enabled 设置。

../../_images/openxr_enabled.webp

你也可以在这里找到其他几个与 OpenXR 相关的设置。不过要注意,这些选项在你的应用程序运行期间是无法修改的。目前的默认设置足够让我们起步了,但如果你想了解这里面每个选项的具体作用,可以查看 OpenXR 设置 获取更多详细信息。

你还需要前往项目设置的 XR > Shaders (XR > 着色器)选项,并勾选 Enabled (启用)复选框来激活它们。完成之后,记得点击 Save & Restart (保存并重启)按钮哦。

../../_images/xr_shaders.webp

警告

许多后期处理特效(post process effects)目前还没有更新以支持立体渲染(stereoscopic rendering)。如果强行使用这些特效,可能会产生不良的负面影响。

设置 XR 场景

每一个 XR 应用至少都需要一个 XROrigin3D 节点和一个 XRCamera3D 节点。大多数应用还会配备两个 XRController3D 节点,分别对应左手和右手。请记住,摄像机节点和手柄控制器节点都必须是原点节点(Origin Node)的子节点。新建一个场景并添加这些节点,然后把两个手柄控制器节点分别重命名为 LeftHandRightHand ,你的场景结构看起来应该像下面这样:

../../_images/xr_basic_scene.webp

这些警告图标是正常的(预期会出现的),等你配置好手柄控制器后,它们就会自动消失啦。现在,请选中左手(left hand)节点,并按照下面的方式进行设置:

../../_images/xr_left_hand.webp

将脚本添加到节点:

../../_images/xr_right_hand.webp

现在,所有节点都在场景的平面上,它们将在运行时自动正确定位。为了帮助开发期间的调试,可以将相机向上移动,再把 y 轴设置为 1.7,并将控制器节点分别移动到 -0.5, 1.0, -0.5(左手)和 0.5, 1.0, -0.5(右手)。

接下来,我们需要给根节点(root node)添加一个脚本。把下面的代码复制到这个脚本里:

extends Node3D

var xr_interface: XRInterface

func _ready():
    xr_interface = XRServer.find_interface("OpenXR")
    if xr_interface and xr_interface.is_initialized():
        print("OpenXR initialized successfully")

        # Turn off v-sync!
        DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

        # Change our main viewport to output to the HMD
        get_viewport().use_xr = true
    else:
        print("OpenXR not initialized, please check if your headset is connected")

上述代码片段假设我们正在使用 OpenXR,如果你希望使用其他接口,可以更改 find_interface 调用。

警告

正如你看到的,我们在代码中关闭了垂直同步(DisplayServer.VSYNC_DISABLED)。在使用 OpenXR 时,将渲染结果输出到一个头戴显示器(HMD)通常需要以 90Hz 或更高的频率运行。如果你的显示器是 60Hz 且开启了垂直同步,那么输出将限制在每秒 60 帧。

像 OpenXR 这样的 XR 接口会执行它们自己的同步。

同时请注意,默认情况下物理引擎以 60Hz 运行,渲染和物理帧数不一致可能会导致物理效果不流畅。你应该将 Engine.physics_ticks_per_second 设置为更高的值。

如果你现在就运行项目,虽然一切功能都能正常运作,但你只会身处一片漆黑的世界里。所以,为了完善我们的起步场景,请在你的场景里添加一个 DirectionalLight3D (定向光3D)节点和一个 a WorldEnvironment (世界环境)节点。你可能还想在每个手柄控制器节点下,分别添加一个网格实例(mesh instance)作为子节点,这样就能暂时看到手柄的模型了。另外,记得一定要在你的世界环境节点里配置好天空(sky)哦。

完成配置后运行项目,你应该会漂浮在某个空间中,并能够四处观察。

备注

虽然传统的关卡切换方式确实可以用在 XR 应用中(也就是在每个关卡里都重复搭建一遍 XR 场景配置),但大多数人发现,更简单省事的做法是:只配置一次 XR 环境,然后把各个关卡作为子场景(subscene)来加载。如果你确实要切换完整场景,并且在每个场景里都复制了 XR 的配置,那请务必确保不要多次运行 initialize (初始化)函数。因为根据不同的 XR 接口,重复初始化可能会带来难以预测的后果。

在接下来的基础教程中,我们将创建一个只使用单场景的游戏作为练习。