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.
Checking the stable version of the documentation...
OpenXR 渲染模型
OpenXR API 设计的一个核心原则,就是尽可能地不依赖任何特定平台。一个绝佳的例子就是 OpenXR 的‘动作映射系统’——如果 XR 运行时找不到当前正在使用的硬件所对应的交互配置,它就必须回退并支持一套核心的交互配置方案。这样一来,即使应用程序发布后出现了全新的硬件,或者开发者在开发时根本没有接触过某款设备,OpenXR 应用程序依然能够正常运行。
这就导致了一个结果:应用开发者无法确切地知道用户到底在使用什么硬件,因为 XR 运行时可能会在模拟(伪装成)其他的设备。因此,开发者也就无法在应用中展示与实际硬件相关的任何内容,而最典型的应用场景,就是展示用户当前正握着的手柄。
展示正确的手柄模型,并让它们的位置精准无误,对于营造真正的沉浸感至关重要。
这时候,OpenXR 的 render models API 就派上用场了。通过这个 API,我们可以向 XR 运行时请求获取与当前正在使用的物理硬件完全匹配的 3D 资源。此外,该 API 还能让我们查询到这些硬件在追踪空间内的具体位置,以及硬件各个子组件(比如按键、摇杆等)的准确位置。
比如,我们可以精准地定位并给扳机键(trigger)加上动画,或者展示出按键被按下的状态。
对于那些支持 controller data source for hand tracking 的 XR 运行时,我们甚至可以根据手柄的实际形状,来正确地定位用户的手指和手掌。但请注意,这项功能需要结合 hand joints motion range extension 一起使用,这样才能有效防止手指穿模。
OpenXR 渲染模型节点
你可以使用 OpenXRRenderModelManager 节点来自动化处理大部分的渲染模型功能。这个节点会实时追踪当前由 XR 运行时所提供的、所有处于活跃状态的渲染模型。
该节点会为每个活跃渲染模型创建子节点,从而显示对应的渲染模型。
这个节点的父级层级中,必须包含一个 XROrigin3D 节点。
如果将 tracker 设置为 Any ,我们的节点就会显示当前所有正在被追踪的渲染模型。在这种情况下,这个节点必须得是 XROrigin3D 节点的直接子级。
如果将 tracker 设置为 None set ,我们的节点就只会显示那些没有被识别出具体追踪器的渲染模型。在这种情况下,这个节点必须得是 XROrigin3D 节点的直接子级。
如果将 tracker 设置为 Left Hand 或 Right Hand ,我们的节点就会只负责显示与左手或右手相关的渲染模型。在这种情况下,我们的节点就可以被放置在场景树更深的位置。
警告
对于大多数 XR 运行时来说,这意味着渲染模型代表的确实是用户手中正握着的控制器,但这并不能百分百保证。有些 XR 运行时即使手柄当前并没有被握住,只要它还在被系统追踪(比如放在桌上但没关机),就会一直把追踪器设定为左手或右手。你应该务必对此进行测试,否则可能会导致意想不到的错误行为。
在这种情况下,我们也可以通过将 make_local_to_pose 属性设置为某个姿态动作(pose action),从而在动作映射中为该姿态指定一个动作。将这个属性与同样使用该姿态的 XRController3D 节点配合起来使用,你就可以额外增加一层控制,让你能够自由地偏离手柄及其对应渲染模型原本的追踪位置(具体用法请看下面的示例)。
备注
将上述内容与手部追踪结合起来,确实会带来一个问题,那就是手部追踪与动作映射(action map)系统是完全独立的。因此,你需要把手部追踪和手柄追踪的姿态(poses)结合起来,才能正确地给渲染模型加上偏移量。
这超出了本文档的范围。
渲染模型管理器示例
你可以下载 our render models demo ,它完整实现了下面将要介绍的这套设置。
在这个设置中,我们在 XROrigin3D 节点的正下方找到了一个 OpenXRRenderModelManager 节点。这个节点的 target 属性被设置成了 None set ,它的作用是负责显示所有当前与我们的左手或右手控制器无关的渲染模型。
接下来你会看到左手和右手的设置是完全一样的,所以我们这里就只专注于讲解左手的部分。
我们这里用了一个 XRController3D 节点,它会负责追踪我们手部的位置。
备注
在这个示例中,我们使用的是 grip 姿势。不过, palm 姿势在适用性和可预测性上其实更有优势,只是并非所有的 XR 运行时(runtime)都支持它。如果你想根据设备的支持情况在这两种姿势之间自动切换,可以去看看手部追踪的示例项目,里面有现成的解决方案。
在这个节点的子级里,我们放了一个 AnimatableBody3D 节点。它会跟随被追踪的手部位置, 但 同时也能和物理对象发生交互,从而防止玩家的手穿墙或者穿过其他物体。这个节点上带有一个碰撞形状(collision shape),把整个手部都包裹了起来。
备注
务必设置好物理优先级(physics priority),确保这段逻辑在任何移动 XROrigin3D 节点的物理逻辑之后执行,否则手部动作就会滞后一帧。
以下脚本展示了一个基本实现,你可以在此基础上进行扩展。
class_name CollisionHands3D
extends AnimatableBody3D
func _ready():
# Make sure these are set correctly.
top_level = true
sync_to_physics = false
process_physics_priority = -90
func _physics_process(_delta):
# Follow our parent node around.
var dest_transform = get_parent().global_transform
# We just apply rotation for this example.
global_basis = dest_transform.basis
# Attempt to move to where our tracked hand is.
move_and_collide(dest_transform.origin - global_position)
最后,我们会看到另一个 OpenXRRenderModelManager 节点,它的 target 被设置成了对应的手(比如左手或右手),而 make_local_to_pose 则被设置成了正确的姿态。这样做是为了确保,如果我们的碰撞处理器(collision handler)改变了手的位置,与该手相关的渲染模型依然能被正确显示,并且保持正确的偏移量。
渲染模型节点
OpenXRRenderModel 节点封装了所有的逻辑,专门用来显示和定位由渲染模型 API 提供的特定渲染模型。
这个节点的实例是由我们上面提到的渲染模型管理器节点(render model manager node)自动添加的,但如果你愿意,也可以直接和它们进行交互。
每当 Godot 获取到有关新渲染模型的信息时,它都会创建一个 RID(资源标识符)来指向(或引用)该渲染模型。
只要把那个 RID 赋值给这个节点上的 render_model 属性,该节点就会开始显示这个渲染模型。同时,它还会自动管理好模型的变换(transform),确保模型被放置在正确的位置,并让所有的子部件(比如手柄上的按键、摇杆等)动起来。
get_top_level_path 函数会返回这个渲染模型当前关联的顶层路径。这个路径通常会指向左手或右手。由于用户拿起或放下控制器时,顶层路径可能会被设置或清空,因此你可以连接(connect) render_model_top_level_path_changes 信号,并对这些变化做出相应的反应。
根据你对 OpenXRRenderModelManager 节点的设置,当渲染模型的顶层路径(top level path)发生变化时,它们会被自动移除或重新添加。
后端访问
上面我们详细介绍的那些节点已经帮我们处理好了所有的显示逻辑,但你完全可以直接去操作驱动这些逻辑的底层数据,从而打造出属于你自己的实现方案。
为此,你可以访问 OpenXRRenderModelExtension 单例。
这个对象还允许你调用它身上的 is_active 函数,来查询当前使用的设备是否支持并启用了渲染模型。
这套内置逻辑实现了 interaction render model API ,该 API 会列出所有与控制器及类似设备相关、且存在于动作映射(action map)中的渲染模型。它会自动创建和移除通过此 API 暴露出来的渲染模型实体。
随着其他扩展功能陆续推出,你可以通过 GDExtension 插件来实现它们。这类插件可以调用 render_model_create 和 render_model_destroy ,从而创建出对应的对象,让你能通过核心渲染模型 API 来调用和使用该渲染模型。
你不应该在此逻辑之外销毁渲染模型。
你可以连接(connect) render_model_added 和 render_model_removed 这两个信号,以便在添加或移除新的渲染模型时收到通知。
下面是使用这个 API 所需的核心方法:
函数 |
描述 |
|---|---|
render_model_get_all |
提供一个包含所有正在被追踪的渲染模型的 RID 数组。 |
render_model_new_scene_instance |
提供一个全新的场景,其中包含了显示该渲染模型所需的所有网格(meshes)。 |
render_model_get_subaction_paths |
提供一个来自你的动作映射(action map)、且与此渲染模式相关的子动作路径(subaction paths)列表。 |
render_model_get_top_level_path |
返回与此渲染模型关联的顶层路径(如果有的话)。请使用 |
render_model_get_confidence |
返回该渲染模型(render model)追踪数据的追踪置信度(confidence)。 |
render_model_get_root_transform |
返回当前参考空间(reference space)中,该渲染模型(render model)的根变换(root transform)。你可以用它来把这个渲染模型放置到空间中。 |
render_model_get_animatable_node_count |
返回渲染模型场景中可动画化的节点的数量 |
render_model_get_animatable_node_name |
返回我们可以进行动画处理的节点名称。需要注意的是,这个节点在场景里可能位于任意层级的深处。 |
render_model_is_animatable_node_visible |
若该动画节点应该显示,则返回 true |
render_model_get_animatable_node_transform |
返回该可动画节点(animatable node)的变换(transform)。这是一个可以直接应用的局部变换。 |