☰
Godot OpenXRRenderModelExtension 渲染模型扩展 API 全解析:从控制器可视化到底层数据访问
2026/10/13 3:43:07 网站建设 项目流程
  • 文档
  • 教程
  • 游戏开发

【免费下载链接】godot-docs

Godot Engine official documentation

项目地址:https://gitcode.com/GitHub_Trending/go/godot-docs
点击查看免费下载

本文以 Godot 引擎官方文档中 OpenXRRenderModelExtension 类参考为骨架,系统讲解 OpenXR 渲染模型扩展在 Godot 中的实现原理与编程接口。读者将掌握:渲染模型扩展的激活检测、RID 驱动的模型生命周期管理(创建/销毁/枚举)、可动画节点与跟踪置信度查询,以及如何通过该扩展绕开管理器节点、自主实现控制器(或手柄)3D 模型的显示与驱动。

为什么需要渲染模型扩展:OpenXR 平台无关设计的直接后果

OpenXR 的 API 设计核心原则之一是“尽可能平台无关”,其 action map 系统要求 XR 运行时在找不到与硬件匹配的交互配置(interaction profile)时,回退到核心交互配置。这保证了应用在开发时尚不存在的硬件上也能正常运行,但代价是:应用开发者无法确知用户手中到底握着什么硬件——运行时可能在“模仿”其他设备。

由此带来一个直接问题:开发者无法按真实硬件展示相关内容,最常见的就是无法向用户显示其正在握持的控制器模型。而正确地显示控制器模型、并把模型放到正确的位置,对沉浸感至关重要。

这就是 OpenXR 渲染模型 API(XR_EXT_render_models)登场的理由:它允许应用向 XR 运行时查询与物理硬件相符的 3D 资产,同时查询该硬件在跟踪体积内的位置,以及硬件子组件的正确摆放——例如正确摆放并动画化扳机(trigger)、显示按键被按下的状态。对于支持控制器手部跟踪数据源扩展(XR_EXT_hand_tracking_data_source)的运行时,还能依据控制器形状正确摆放用户手指与手部,并与手部关节运动范围扩展(XR_EXT_hand_joints_motion_range)配合,防止手指穿模。

在 Godot 中,这一能力由OpenXR 渲染模型扩展(OpenXRRenderModelExtension)实现。它继承自 OpenXRExtensionWrapper(进而继承自Object),是该扩展家族的一员——OpenXRExtensionWrapper允许通过 GDExtension 实现 OpenXR 扩展,注册方式为register_extension_wrapper(),且必须在 OpenXR 初始化完成前注册。

快速上手:先看整套渲染模型工作流

在深入OpenXRRenderModelExtension的 API 之前,先明确它在整个渲染模型体系中的位置。Godot 为渲染模型提供了三件套:

类继承职责
OpenXRRenderModelManagerNode3D辅助节点,自动为检测到的控制器/手持设备创建OpenXRRenderModel子节点,并在其失活时移除
OpenXRRenderModelNode3D单个渲染模型的显示节点:加载关联 GLTF、处理动画数据、摆放与定位
OpenXRRenderModelExtensionOpenXRExtensionWrapper底层扩展封装,维护活跃渲染模型列表并提供数据访问接口(本文主角)

使用管理器节点的常规路径

OpenXRRenderModelManager 节点能自动化大部分渲染模型功能:它跟踪 XR 运行时当前提供的活跃渲染模型,并为每个活跃模型创建子节点来显示。其tracker属性决定筛选范围,取值如下:

常量值含义
RENDER_MODEL_TRACKER_ANY0显示所有活跃渲染模型,节点必须是XROrigin3D的直接子节点
RENDER_MODEL_TRACKER_NONE_SET1仅显示未关联任何已识别跟踪器的模型,也必须是XROrigin3D的直接子节点
RENDER_MODEL_TRACKER_LEFT_HAND2仅显示与左手跟踪器相关的模型,可放在场景树更深层
RENDER_MODEL_TRACKER_RIGHT_HAND3仅显示与右手跟踪器相关的模型,可放在场景树更深层

一个关键约束:管理器节点必须以 XROrigin3D 节点为祖先。此外需要特别注意的是,多数 XR 运行时中渲染模型代表“用户正握持的控制器”,但这并非保证——某些运行时即使用户没有握持、只是控制器处于被跟踪状态,也会把跟踪器设置为左手或右手,开发者必须实测,否则会出现非预期行为。

当tracker指向左手/右手时,还可以通过make_local_to_pose属性指定 action map 中的一个 pose action,让渲染模型相对于该 pose 本地化摆放(调整渲染模型容器节点的位置)。配合使用同一 pose 的 XRController3D 节点,即可添加一层“偏离控制器与渲染模型跟踪位置”的自定义逻辑(典型如碰撞阻挡)。注意:此用法若与手部跟踪叠加,会引入“手部跟踪完全独立于 action map 系统”的耦合问题,需自行将手部跟踪与控制器跟踪的 pose 组合以正确偏移渲染模型。

官方渲染模型演示项目(godot-demo-projects仓库的xr/openxr_render_models)实现了完整示例:场景中一个OpenXRRenderModelManager直接挂在XROrigin3D下、target设为None set,负责显示所有与左右手控制器无关的模型;左右手各有独立的XRController3D(示例使用grippose 跟踪手的位置,因为palmpose 虽更合适但并非所有运行时都支持),其子节点为带碰撞形状的AnimatableBody3D,用于让手跟随跟踪位置但被物理对象阻挡、防止穿墙。碰撞脚本要点如下:

class_name CollisionHands3D extends AnimatableBody3D func _ready(): # 确保以下设置正确。 top_level = true sync_to_physics = false process_physics_priority = -90 func _physics_process(_delta): # 跟随父节点移动。 var dest_transform = get_parent().global_transform # 本示例只应用旋转。 global_basis = dest_transform.basis # 尝试移动到手被跟踪到的位置。 move_and_collide(dest_transform.origin - global_position)

注意:务必设置物理优先级(示例为process_physics_priority = -90),使该逻辑在移动XROrigin3D节点或手的物理逻辑之后执行,否则手会滞后一帧。之后另一个OpenXRRenderModelManager节点把target设为对应手、make_local_to_pose设为正确 pose,即可在碰撞处理器改变位置时仍正确显示并偏移该手的渲染模型。

直接使用渲染模型节点的路径

OpenXRRenderModel 节点实现了显示与定位单个渲染模型的全部逻辑,通常由管理器节点自动添加,但也可以直接交互:每当 Godot 获知一个新的渲染模型,就会创建一个RID来引用它;把该RID赋给节点的render_model属性,节点即开始显示该模型并管理其摆放变换与子对象动画。get_top_level_path()返回该模型关联的顶层路径(指向左手或右手)。由于顶层路径会随用户拿起/放下控制器而被设置或清除,可连接render_model_top_level_path_changed信号做出响应。

OpenXRRenderModelExtension 类概览

回到本文主角。OpenXRRenderModelExtension的官方描述是:实现 OpenXR 渲染模型扩展;若启用,它维护一份活跃渲染模型的列表,并提供渲染模型数据的访问接口。从源码结构看,它作为 OpenXR 模块内置的扩展包装器单例存在,与OpenXRAndroidThreadSettingsExtension、OpenXRFrameSynthesisExtension、OpenXRSpatialAnchorCapability等并列,都是OpenXRExtensionWrapper的子类。

继承链:OpenXRRenderModelExtension→OpenXRExtensionWrapper→Object。

它对外暴露三类成员:3 个信号、1 个状态查询方法、11 个渲染模型操作方法。下面逐一展开。

信号:感知渲染模型的增删与顶层路径变化

render_model_added

signal render_model_added(render_model: RID)

当一个新的渲染模型被添加时发出。render_model参数是新模型的RID。订阅此信号即可在运行时感知“新设备出现”(例如用户拿起另一只手柄、运行时新暴露了一个模型),无需轮询。

render_model_removed

signal render_model_removed(render_model: RID)

当渲染模型被移除时发出,参数同样是该模型的RID。与render_model_added成对出现,用于清理自己创建的显示节点或资源。

render_model_top_level_path_changed

signal render_model_top_level_path_changed(render_model: RID)

当某个渲染模型关联的顶层路径改变时发出。如前所述,顶层路径标识该模型与玩家左手还是右手(或身体其他部位)相关,会随控制器被拿起/放下而变化。官方教程建议:连接此信号并在变化时做出反应,同时配合管理器节点的tracker设置,顶层路径变化往往意味着模型要被移除或重新添加。

状态查询:is_active

func is_active() -> bool

返回true表示 OpenXR 的渲染模型扩展已被支持且已启用。

重要限制:此方法仅在 OpenXR 初始化完成后才返回有效值。因此不要在游戏启动早期、XR 会话尚未建立时依赖它做流程分支;合理的做法是在 XR 会话就绪后再查询,例如在 OpenXR 状态进入focused(游戏实际运行状态)后再调用。官方教程也将is_active定位为“查询当前设备是否支持并启用了渲染模型”的手段。

渲染模型生命周期:create 与 destroy

render_model_create

func render_model_create(render_model_id: int) -> RID

使用一个渲染模型 id 在 OpenXR 内部创建一个渲染模型对象,返回引用该对象的RID。

官方注释明确指出:此函数是为依赖渲染模型扩展、并提供渲染模型 id 供其使用的其他 OpenXR 扩展暴露的。也就是说,它不是一个面向普通游戏逻辑的“创建任意模型”接口——普通场景中,模型的创建由内置逻辑(见下文“内置逻辑”)自动完成。典型的调用者是 GDExtension 插件:当新的扩展类型可用时,插件调用render_model_create创建对象,使该模型可通过核心渲染模型 API 访问。

render_model_destroy

func render_model_destroy(render_model: RID)

销毁一个先前通过render_model_create()创建的渲染模型对象。同样是为依赖型扩展暴露的接口。官方教程有一句重要警告:不应在本逻辑(内置的渲染模型管理逻辑)之外销毁渲染模型——这意味着如果你没有通过render_model_create亲手创建它,就不要手动调用 destroy,避免与内置管理逻辑产生生命周期冲突。

模型枚举:render_model_get_all

func render_model_get_all() -> Array[RID]

返回当前通过本扩展注册的所有活跃渲染模型的RID数组。这是无管理器场景下自主实现显示逻辑的起点:拿到全部 RID 后,可以为每个 RID 创建 OpenXRRenderModel 节点并赋值其render_model属性。OpenXRRenderModelManager文档也提示过这一替代方案:若想对显示逻辑获得更多控制权,可以调用render_model_get_all()获取活跃模型 id 列表,再为每个 id 创建OpenXRRenderModel实例。

场景实例化:render_model_new_scene_instance

func render_model_new_scene_instance(render_model: RID) -> Node3D

返回一个子场景实例,其中包含所有用于可视化该渲染模型的MeshInstance3D节点。这是把“数据”变成“可见 3D 模型”的关键一步:拿到实例后将其加入场景树即可看到控制器/设备模型。官方教程的功能表中,该方法被描述为“提供一个包含显示渲染模型所需的全部网格的新场景”。

路径信息:顶层路径与子动作路径

render_model_get_top_level_path

func render_model_get_top_level_path(render_model: RID) -> String

返回与该渲染模型关联的顶层路径。若提供了该路径,它标识此渲染模型与玩家手部还是其他身体部位相关——实践中通常指向左手或右手。该路径可被设置或清除,因此要跟踪其变化需连接render_model_top_level_path_changed信号。

render_model_get_subaction_paths

func render_model_get_subaction_paths(render_model: RID) -> PackedStringArray

返回该渲染模型当前活跃的子动作(subaction)路径列表。子动作路径来自你的 action map。官方注释特别说明:如果设备上绑定的动作与建议交互绑定(suggested interaction bindings)中提供的不同,此信息反映的是该设备正在模仿的交互绑定相关路径。结合OpenXRRenderModelManager的tracker/make_local_to_pose机制可以推断:子动作路径用于建立渲染模型与 action map 中 pose action 的关联,从而支持“模型跟随手势、可叠加自定义偏移”这类高级用法。

位姿数据:根变换与跟踪置信度

render_model_get_root_transform

func render_model_get_root_transform(render_model: RID) -> Transform3D

返回渲染模型的根变换:即相对于 XROrigin3D 节点、被跟踪到的位置。官方教程的功能表进一步说明:这是“当前参考空间内的根变换,可用于把渲染模型放置在空间中”——换句话说,拿到这个变换即可把模型实例摆到正确的世界位置。

render_model_get_confidence

func render_model_get_confidence(render_model: RID) -> TrackingConfidence

返回该渲染模型跟踪数据的跟踪置信度。返回类型是XRPose的TrackingConfidence枚举。可用它判断当前跟踪数据是否可靠,例如置信度不足时隐藏模型或切换为推测姿态,避免把错误位置展示给用户。

可动画节点:让控制器动起来

一个渲染模型的子场景中,部分节点是可动画的(例如扳机、摇杆、按键)。以下四个方法构成完整的可动画节点查询接口。

render_model_get_animatable_node_count

func render_model_get_animatable_node_count(render_model: RID) -> int

返回该渲染模型拥有的可动画节点数量。枚举可动画节点时用它作为遍历上界。

render_model_get_animatable_node_name

func render_model_get_animatable_node_name(render_model: RID, index: int) -> String

返回指定索引的可动画节点名称。官方教程补充说明:该节点在场景中可能位于任意深度(不一定是直接子节点),所以要通过名称在场景树中查找,而不是假设固定层级。

render_model_get_animatable_node_transform

func render_model_get_animatable_node_transform(render_model: RID, index: int) -> Transform3D

返回指定可动画节点的当前局部变换。关键特性:每一帧都会更新。官方教程的功能表指出这是“可直接应用的局部变换”——把它直接赋给场景实例中对应骨骼/网格节点,即可驱动扳机扣动、按键按下等动画。典型逐帧流程是:先用render_model_get_animatable_node_count获取数量,再循环调用render_model_get_animatable_node_name/render_model_get_animatable_node_transform/render_model_is_animatable_node_visible同步状态。

render_model_is_animatable_node_visible

func render_model_is_animatable_node_visible(render_model: RID, index: int) -> bool

返回指定可动画节点当前是否应该可见。某些部件在特定交互状态下会隐藏(例如未握持时某些部件收起),逐帧查询并同步可见性,可确保模型状态与真实硬件一致。

核心 API 速查表

官方教程为底层访问归纳了如下核心函数清单,可直接作为自主实现时的索引:

函数作用
render_model_get_all提供所有被跟踪渲染模型的 RID 数组
render_model_new_scene_instance提供包含显示渲染模型所需全部网格的新场景
render_model_get_subaction_paths提供 action map 中与该渲染模型相关的子动作路径列表
render_model_get_top_level_path返回该渲染模型关联的顶层路径(若有);用render_model_top_level_path_changed信号响应变化
render_model_get_confidence返回该渲染模型跟踪数据的跟踪置信度
render_model_get_root_transform返回当前参考空间内该模型的根变换,用于把模型放置到空间中
render_model_get_animatable_node_count返回渲染模型场景中可动画节点的数量
render_model_get_animatable_node_name返回可动画节点名称(该节点可能在场景任意层级)
render_model_is_animatable_node_visible返回该可动画节点是否应可见
render_model_get_animatable_node_transform返回可动画节点的局部变换,可直接应用

底层原理:扩展如何驱动整条链路

从类继承关系与配套文档可以还原OpenXRRenderModelExtension在引擎中的工作方式:

  • 注册与激活:它作为OpenXRExtensionWrapper的子类,在核心模块初始化阶段通过register_extension_wrapper()注册。扩展包装器会向 OpenXR 请求自身依赖的扩展名(对应_get_requested_extensions()的机制),并随 OpenXR 实例/会话的创建、销毁以及各状态切换收到回调(如_on_instance_created、_on_session_created、_on_process等)。因此is_active()只有在这些生命周期推进到 OpenXR 初始化完成后才返回有效值。
  • 内置逻辑与自动管理:官方教程明确,内置逻辑实现的是交互渲染模型 API(XR_EXT_interaction_render_model),它列出 action map 中与控制器及类似设备相关的所有渲染模型,并自动创建与移除经由该 API 暴露的渲染模型实体。这正是render_model_added/render_model_removed信号、以及OpenXRRenderModelManager能自动增删显示节点的数据来源。
  • 扩展点:随着更多扩展类型可用,可通过 GDExtension 插件实现它们——插件调用render_model_create与render_model_destroy创建/销毁对象,使对应模型能通过核心渲染模型 API 被访问。这解释了为什么这两个方法是“为依赖型扩展暴露的”,也解释了“不应在本逻辑之外销毁渲染模型”的约束:销毁必须由创建方(内置逻辑或插件)负责,保持生命周期一致。
  • 线程安全上下文:由于OpenXRExtensionWrapper的部分虚方法(如_on_pre_render、_get_composition_layer)会在渲染线程被调用,而 OpenXR 的渲染进程在ProjectSettings.rendering/driver/threads/thread_model设置为 Separate 时会运行于独立线程,依赖该扩展的渲染数据写入建议通过RenderingServer.call_on_render_thread()调度,避免线程竞争与帧错位。这一提醒同样适用于基于本扩展的渲染模型数据处理。

自主实现示例:不用管理器,自己驱动渲染模型

结合上述 API,可以在不使用OpenXRRenderModelManager的情况下自主实现渲染模型显示。核心流程为:查询is_active()确认可用 → 用render_model_get_all()枚举模型 → 为每个 RID 创建场景实例并放置 → 每帧同步根变换与可动画节点。骨架代码:

extends Node3D # 假设这是一个在 XR 会话就绪后运行的自定义渲染模型控制器 func _setup_render_models(): var ext := OpenXRRenderModelExtension # 引擎内置单例 if not ext.is_active(): return # 设备不支持或扩展未启用 for render_model in ext.render_model_get_all(): # 获取包含全部 MeshInstance3D 的子场景 var instance := ext.render_model_new_scene_instance(render_model) add_child(instance) # 用根变换把模型放到被跟踪的正确位置(相对 XROrigin3D) instance.global_transform = ext.render_model_get_root_transform(render_model) # 连接信号感知增删与顶层路径变化 ext.render_model_added.connect(_on_model_added) ext.render_model_removed.connect(_on_model_removed) ext.render_model_top_level_path_changed.connect(_on_top_level_path_changed) func _process(_delta): var ext := OpenXRRenderModelExtension for render_model in ext.render_model_get_all(): # 每帧同步根变换 # 同步可动画节点:循环 count 个节点,按名称在场景中查找后应用局部变换与可见性 var count := ext.render_model_get_animatable_node_count(render_model) for i in count: var node_name := ext.render_model_get_animatable_node_name(render_model, i) var node_transform := ext.render_model_get_animatable_node_transform(render_model, i) var visible := ext.render_model_is_animatable_node_visible(render_model, i) # 在 instance 场景中找到 node_name 对应的节点,应用 node_transform 与 visible

说明:上述代码为基于官方 API 文档的演示骨架,实际使用时还需处理is_active()的时序(必须在 OpenXR 初始化完成后)、模型增删时的节点清理,以及子场景中可动画节点名称到具体骨骼/网格节点的映射。

延伸阅读

  • 渲染模型完整教程(含管理器节点布置、碰撞手示例):tutorials/xr/openxr_render_models.rst
  • 管理器辅助节点:OpenXRRenderModelManager
  • 单个模型显示节点:OpenXRRenderModel
  • 扩展包装基类与注册机制:OpenXRExtensionWrapper
  • OpenXR 项目设置概览(含渲染模型功能入口):tutorials/xr/openxr_settings.rst
  • 文档
  • 教程
  • 游戏开发

【免费下载链接】godot-docs

Godot Engine official documentation

项目地址:https://gitcode.com/GitHub_Trending/go/godot-docs
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询