开源OpenXR引擎:Monado XR Runtime开发配置及关键模块说明(可用于自研VRAR眼镜设备或pico、queset等量产设备)
一 OpenXR Monado 简介
Monado官网
OpenXR 是一套跨平台的 VR/AR 应用程序接口,定义了应用与硬件交互的统一规范。
Monado 作为一套开源XR运行时,完整实现了 OpenXR 的核心接口,让开发者无需针对不同硬件编写专用代码,只需调用 OpenXR 标准 API 即可运行在支持 Monado 的设备上。Monado主要能力包括与 VR/AR 硬件(如头显、手柄、传感器等)直接通信,将硬件数据(如位置姿态、按键输入、传感器数据)转换为 OpenXR 标准格式,提供给上层应用
Monado 的 SLAM 定位基于 视觉惯性里程计(VIO),集成了 Kimera-VIO、ORB-SLAM3 和 Basalt等开源方案,这部分也是Monado的核心价值:兼容多个性能优异的开源方案
不同于Pico、QuestHololens、Valve等Runtime,Monado 是开源的OpenXR 运行时,适用领域包括:
- Linux 平台的 XR 开发(尤其是开源硬件或小众设备)
- 自定义设备适配或学术研究(需手动开发驱动)
- 不依赖商业引擎的原生(C/C++)OpenXR 应用开发。
二 工程及环境配置
2.1 Monado(XR Runtime运行时)
- 克隆工程
git clone https://gitlab.freedesktop.org/monado/monado.git - 下载依赖eigen-5.0.0
https://eigen.tuxfamily.org/index.php?title=Main_Page - 打开monado目录下的local.properties文件,在文件末尾添加一行eigenIncludeDir=D:\proj\monado\eigen-5.0.0

- 配置settings.gradle的仓库,防止编译失败
dependencyResolutionManagement {// 禁止子项目单独添加仓库,统一在此管理repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {// 阿里云公共仓库(优先使用,加速国内下载)maven { url 'https://maven.aliyun.com/repository/public' }// 阿里云 Google 仓库(同步 Google 官方库,避免访问超时)maven { url 'https://maven.aliyun.com/repository/google' }// 阿里云 Gradle 插件仓库(补充插件下载)maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }// 官方仓库(作为 fallback,确保依赖完整性)google()mavenCentral()}
}
-
如果报错需要安装glsl工具后安装
https://github.com/KhronosGroup/glslang/releases/tag/main-tot
将bin\glslangValidator.exe加入到环境变量中,需要在cmd中确定安装成功
glslangValidator --version
Build->clean project 后重新build即可 -
报错error when building with cmake using D:\proj\monado\monado\CMakeLists.txt: – The C compiler identification is Clang 17.0.2
找到src.xrt.targets.openxr_android 中的build.gradle
添加glsl工具配置

-
如果配置都没问题,build成功后就可以看到软件界面里

2.2 Broker(Runtime 配置器)
当前系统存在多个XR Runtime时,如果不指定配置则会产生乱启动现象。我们可以通过配置Broker来实现实时切换XR Runtime的能力。(同一时刻只能启动一个Runtime)
拉取工程: git clone https://gitlab.freedesktop.org/monado/utilities/openxr-android-broker.git
等待sync结束
2.3 hello_xr(示例apk)
- 拉取工程:git clone https://github.com/KhronosGroup/OpenXR-SDK-Source.git
- android打开工程。选择 OpenXR-SDK-Source/src/tests/hello_xr目录
等待 Gradle 同步完成(首次同步可能需要下载依赖,确保网络畅通)。
三 编译,运行
3.1 编译
在这三个目录下分别执行 gradlew assembleDebug
openxr/monado
openxr/openxr-android-broker
openxr/OpenXR-SDK-Source/src/tests/hello_xr
经过编译我们目前得到了三个apk,分别是
../src/xrt/targets/openxr_android/build/outputs/apk/outOfProcess/debug/openxr_android-outOfProcess-debug.apk
../installable_runtime_broker/build/outputs/apk/debug/installable_runtime_broker-debug.apk
../src/tests/hello_xr/build/outputs/apk/ OpenGLES/debug/hello_xr-OpenGLES-debug.apk
3.2 运行测试
分别安装到xr眼镜主机后,先执行monado,再执行boroker并在界面中勾选monado选项。


最后执行hello就能看到效果了。

五 Monado的服务模式
Monado有两种运行模式:
- 默认打包出来的Monado是“In process模式”,在该模式下gradle 将monado runtime打包成so文件,我们要想使用需要将so文件集成到已有的android项目中。该模式下由于monado与android app使用相同进程,所以数据通信性能更好,运行软件时也无需额外启动服务。
- 另一种模式是out process,该模式下会将Monado单独打包成app,在启动主app之前需要提前启动Monado App。应用通过 IPC(进程间通信)与服务交互,隔离性更好,适合对稳定性要求高的场景(如 VR/AR 应用)
切换模式(In Process -> Out of Process)
- 确保 Monado 支持 OOP 模式
Monado 从 v0.3.0 版本 开始支持 OOP 模式,需确认你的版本是否兼容。若使用源码构建,确保编译时未禁用 OOP 功能(默认启用)。
源码构建时,OOP 相关组件由 src/xrt/ipc 和 src/service 模块实现,无需额外编译选项,默认包含在构建中。 - 启动 Monado 服务进程(monado-service)
Out-of-process 模式依赖独立的 monado-service 进程,需先手动启动或配置为自动启动服务,在终端中运行 Monado 服务(通常安装在 /usr/local/bin 或系统 PATH 路径下):
monado-service
服务启动后会监听 IPC 通道(默认使用 Unix 域套接字),等待应用连接。
验证服务状态:服务启动成功会输出类似日志:
plaintext
[INFO] service: Starting Monado service...
[INFO] ipc: Listening on socket: /run/user/1000/monado-socket
- 配置应用使用 OOP 模式
应用需要通过环境变量或配置文件指定使用 OOP 模式连接 monado-service,而非直接加载 in-process 运行时。
通过环境变量指定:启动 OpenXR 应用前,设置 XR_RUNTIME_JSON 环境变量指向 Monado 的 OOP 配置文件(通常名为 openxr_monado_oop.json):
#假设配置文件路径为 /usr/local/share/openxr/1/openxr_monado_oop.json
export XR_RUNTIME_JSON=/usr/local/share/openxr/1/openxr_monado_oop.json
# 启动你的 OpenXR 应用(如 hello_xr)
./hello_xr
配置文件说明:openxr_monado_oop.json 是 Monado 为 OOP 模式提供的标准配置文件,内容类似:
json
{"file_format_version": "1.0.0","runtime": {"library_path": "libmonado_openxr_ipc_client.so","name": "Monado (Out-of-Process)","version": "1.0.0"}
}
其中 libmonado_openxr_ipc_client.so 是 OOP 模式的客户端库,负责与 monado-service 通信。
4. 验证模式是否生效
查看应用日志:应用启动时若成功连接到 monado-service,会输出类似日志:
plaintext
[INFO] ipc_client: Connecting to Monado service at /run/user/1000/monado-socket
[INFO] ipc_client: Connected to service, session established
查看进程列表:通过 ps aux | grep monado 可看到 monado-service 进程和应用进程分离。
切换模式(Out of process -> In Process)
若需恢复为 in-process 模式,只需将 XR_RUNTIME_JSON 指向 Monado 的 in-process 配置文件(通常为 openxr_monado.json):
export XR_RUNTIME_JSON=/usr/local/share/openxr/1/openxr_monado.json
./hello_xr
五 Monado关键模块
5.1 手势控制:UltraLeap 驱动模块
- Monado 的手势控制主要通过 UltraLeap 驱动 实现,代码位于 src/xrt/drivers/ultraleap/ 目录下。
- 核心脚本与方法: 初始化与配置:ulv2_driver.c 中的 ulv2_create 方法负责初始化手势识别驱动,加载 UltraLeap 传感器并注册回调。 手势数据处理:ulv2_tracker.c 中的 ulv2_update_hand_data 方法解析传感器原始数据,生成手部关节位置、姿态等信息,并通过 OpenXR 接口(如 xrLocateHandJoints)传递给应用。
- 编译开关:通过 CMake 宏 XRT_BUILD_DRIVER_ULV2 控制是否编译该模块,编译后生成静态库drv_ulv2.a,链接到 monado-service 或 openxr-monado 运行时。
5.2 外设输入:设备驱动层与输入子系统
- 外设输入(如手柄、头显)的处理分散在多个设备驱动模块中,核心逻辑位于 src/xrt/drivers/ 目录。
- 设备驱动示例: ValveIndex 手柄:src/xrt/drivers/valve/index_controller.c 中的 index_controller_update_input 方法处理按键、触摸板等输入事件。
- 通用输入抽象:src/xrt/input/目录下的 xrt_input_device.c 定义了输入设备的统一接口,各驱动通过实现 xrt_input_device结构体的回调(如 update_input)上报输入数据。
- 输入事件流转:设备驱动采集输入后,通过 xrt_input_system子系统(src/xrt/input/xrt_input_system.c)转发给 OpenXR 运行时,最终通过
xrGetInputState 等 API 暴露给应用。
5.3 SLAM 定位:视觉惯性模块(重要)
- Monado 的 SLAM 定位基于 视觉惯性里程计(VIO),集成了 Kimera-VIO、ORB-SLAM3 和 Basalt等开源方案,代码位于 src/xrt/tracking/slam/ 目录。
- 核心实现: 传感器数据采集:slam_tracker.c 中的slam_tracker_update 方法从摄像头和 IMU 读取原始数据(如帧图像、加速度、角速度)。
- 定位算法调用:以 Basalt为例,basalt_slam.c 中的 basalt_process_frame 方法执行特征提取、位姿估计和地图构建,输出 6DoF位姿信息。
- 数据同步与融合:slam_sync.c 处理多传感器数据的时间对齐,确保视觉和惯性数据在时间上的一致性。
5.4 运行时刷新率:帧调度与合成
- 刷新率由 运行时的帧调度器 控制,核心逻辑位于 src/xrt/compositor/ 目录。
- 关键机制:硬件刷新率检测:compositor.c 中的 xrt_compositor_get_display_refresh_rate
方法查询头显的原生刷新率(如 90Hz、120Hz)。 - 帧提交与重投影:compositor_frame.c 中的xrt_compositor_submit_frame方法处理应用提交的帧,若应用帧率不足,会通过重投影(Reprojection)技术维持硬件刷新率,避免画面卡顿。
- 调度策略:frame_scheduler.c 实现基于硬件刷新率的帧调度,确保每帧在硬件刷新周期内提交,代码中通过 usleep或事件等待机制控制帧间隔。
