使用MTVerseXR SDK实现VR串流

1、概述

MTVerseXR SDK 是摩尔线程GPU加速的虚拟现实(VR)流媒体平台,专门用于从远程服务器流式传输基于标准OpenXR的应用程序。MTVerseXR可以通过Wi-Fi和USB流式将VR内容从Windows服务器流式传输到XR客户端设备, 使相对性能低的VR客户端可以使用高性能图形服务器的渲染能力。

2、环境准备

MTVerseXR SDK包括服务器驱动程序、客户端SDK和示例客户端应用程序。Windows上运行的服务端和VR头盔内的应用程序共同实现了Windows系统(边缘云)的SteamVR与VR头盔客户端的连接。

2.1 MTVerseXR 服务器驱动

运行平台要求

  • 系统:x86 CPU + Windows10

  • 显卡:摩尔线程MTT S80, MTT S70, 驱动程序 v240.50或更新版本

  • 软件:SteamVR

  • 基于OpenXR的Windows VR应用

2.2 MTVerseXR 客户端SDK

客户端SDK目前只支持Android系统。包含头文件、Android so库文件和资源文件,通过AAR的方式提供。

3、服务器驱动安装和启动

  1. 安装SteamVR:
  2. 安装运行时库
  3. 安装Vulkan驱动:
  4. 安装 MTVerseXR驱动:
    • 以管理员权限运行 MTVerseXRSetup-v1.0.0.exe 安装程序;
    • 按照安装向导的步骤进行安装,在最后一页,记得勾选"注册驱动"。如果未勾选注册驱动,可以通过以管理员权限运行安装目录下的 driver_install.bat 文件来完成注册;
    • 重启计算机
  5. 启动SteamVR
    • 确认MTVerseXR驱动启用, 查看SteamVR 【设置】-【启动/关闭】-【管理加载项】-【MTXR_VHMD】选项是开启状态(如果【设置】页面没有【启动/关闭】,点击【设置】页面的左下角【高级设置】下方的【显示】)

4、客户端开发指南

基于Client SDK 开发App的C++部分大致框架如下:

int main() {
 
   MySetupGLES3-2
   MySetupXRSession
 
   MySetupDeviceDesc(&ddesc);
   MySetupCallbacks(&callbacks);
   MySetupReceiverDesc(&rdesc, ddesc, callbacks)
 
   MTXRCreateReceiver();
   MTXRConnect();
 
   while (!*exiting*) {
      MyPlatformEventHandling();
      if (client_state < connected) {
         MyRenderConnectionProgress();
      }
      else if (client_state == connected) {
         UpdateTrackingState();
         MTXRFetchFrame()
         MTXRRenderFrame(framesFetched);
         MTXRReleaseFrame()
      }
      else {
         MyHandleDisconnect();
         exiting = true;
      }
   }
 
   MTXRDestroyReceiver()
}

请注意,此处的UpdateTrackingState是伪代码中更新位姿回调的占位符。在UpdateTrackingState中,可以处理头盔、手柄的位姿和手柄事件,保存最新的位姿信息,在回调中更新。

Java部分需要在Activity的onCreate中调用MTXRClient.init 初始化客户端SDK组件。

SDK同时提供了工具类MTXRServerDiscover和MTXRUSB。其中MTXRServerDiscover用于探测局域网中的MTVerseXR Server,MTXRUSB用于打开USB配件模式的文件描述句柄,具体参考示例代码。

4.1 引用SDK包

在app的gradle中添加aar依赖。

手动或者使用gradle脚本解压aar文件后,在CMakeLists或者Android.mk中引用SDK的头文件和so文件。解压后头文件的路径为assets\include, so的路径为jni\arm64-v8a。

4.2 初始化

Java部分的初始化:

  • 在App MainActivity的onCreate中调用 MTXRClient.init 完成。

    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.xr);
    // 初始化MTXRClient
    MTXRClient.init(getBaseContext());

      // do super first, as that sets up some native things.
      super.onCreate(savedInstanceState);
    
      boolean usMic = false;
      enableMicrophone(usMic);
      ......
    

    }

C++部分初始化:

  1. OpenGLES 3.2上下文环境初始化(SDK目前仅支持OpenGLES环境下的显示,版本要求>=3.2);

  2. OpenXR Sessioin环境初始化(非必须通过OpenXR);

  3. 获取设备相关参数填充MTXRReceiverDesc的成员变量,包括:显示分辨率、刷新率、音频接收状态等;

  4. 设置MTXRClientCallbacks中需要的回调函数,包括GetTrackingState(必须实现以同步最新的位姿和手柄输入)和GetHapticCallback(手柄震动反馈);

  5. 调用MTXRCreateReceiver 创建Receiver对象,用于和服务器端通信的重要对象;

  6. 调用 MTXRConnect(Wifi连接)或者MTXRConnectUSB(USB连接) 连接服务器。

    void XRScene::Start(const std::string& serverIp, int fd, bool enableMic) {
    m_desc.deviceDesc.posePollFreq = 0;

    m_desc.clientCallbacks.GetTrackingState = [](void context, MTXRVRTrackingState trackingState) {
    reinterpret_cast<XRScene
    >(context)->GetTrackingState(trackingState);
    };
    m_desc.clientCallbacks.ReceiveMicrophoneData = NULL;
    m_desc.clientCallbacks.GetHapticCallback = [](void
    context, MTXRHapticFeedback haptic) {
    XRScene
    that = reinterpret_cast<XRScene*>(context);
    if (NULL != that->m_haptic_callback && NULL != that->m_context) {
    that->m_haptic_callback(that->m_context, haptic);
    }
    };
    ......
    m_desc.deviceDesc.receiveAudio = true;
    m_desc.clientContext = this;
    m_desc.requestedVersion = MTXR_VERSION_DWORD;
    m_desc.deviceDesc.sendAudio = enableMic;
    if (enableMic) {
    m_desc.clientCallbacks.ReceiveMicrophoneData = [](void* context, unsigned char* data, int size)->bool {
    return reinterpret_cast<XRScene*>(context)->ReceiveMicrophoneData(data, size);
    };
    }

    MTXRError ret = MTXRCreateReceiver(&m_desc, &m_receiver);
    if (MTXRError_Success == ret) {
    if (fd > 0) {
    MTXRConnectUSB(m_receiver, fd);
    } else {
    MTXRConnect(m_receiver, serverIp.c_str());
    }
    }
    }

4.3 主循环

MTVerseXR客户端App的主循环中需要根据当前的状态做出不同的行为。

  • Receiver未创建:显示一些交互UI,比如服务器的IP地址,让用户可以选择不同的连接方式。

  • 未连接到服务器:显示加载提示,告知用户正在连接服务器。

  • 成功连接到服务器:开始接收服务器传输的音视频数据。

  • 有可用的视频帧:判断当前是否有可用的视频帧,根据结果显示最新的视频或者缓存的视频帧。

  • 连接断开:用户主动断开连接后,应用需要清理相关资源。

渲染服务器视频流步骤:

  1. 获取可用视频帧:调用MTXRFetchFrame获取当前可用视频数据。

    void XRScene::BeginFrame(int64_t predictedDisplayTime, const MTXRVRTrackingState& pose, const std::vector<XrView>& xrViews, bool showPerfUI) {
    {
    std::lock_guardstd::mutex guard(m_poseLock);
    m_xrState = pose;
    }

    ......
    m_xrFrame = std::make_shared<MTXRFramesFetched>();
    MTXRFetchFrame(m_receiver, m_xrFrame.get(), predictedDisplayTime, 0);
    }

  2. 渲染视频帧:MTXRFetchFrame成功后,调用MTXRRenderFrame显示当前最新视频帧,否则跳过绘制或者显示之前的缓存。注意需要在激活GLES 3.2上下文环境的渲染线程中调用。

    void XRScene::RenderFrame(const XrCompositionLayerProjectionView &layerView, int viewId) {
    MTXRPosef pose;
    pose.position = {layerView.pose.position.x, layerView.pose.position.y, layerView.pose.position.z};
    pose.orientation = {layerView.pose.orientation.x, layerView.pose.orientation.y, layerView.pose.orientation.z, layerView.pose.orientation.w};

    MTXRFovf fov = {layerView.fov.angleLeft, layerView.fov.angleRight, layerView.fov.angleUp, layerView.fov.angleDown};

    MTXRRenderFrame(m_receiver, m_xrFrame.get(), viewId == 0 ? MTXRFrameMask_Left : MTXRFrameMask_Right, &pose, &fov);
    }

    void RenderView(int viewID,
    XrTime predictedDisplayTime,
    std::shared_ptr<XRScene> xrScene,
    const XrCompositionLayerProjectionView &layerView,
    const XrSwapchainImageBaseHeader *swapchainImage,
    int64_t swapchainFormat) override {
    glBindFramebuffer(GL_FRAMEBUFFER, m_swapchainFramebuffer);

    const XrSwapchainImageOpenGLESKHR* swapchainImageGLES = reinterpret_cast<const XrSwapchainImageOpenGLESKHR *>(swapchainImage);
    const uint32_t colorTexture = swapchainImageGLES->image;

    glViewport(static_cast<GLint>(layerView.subImage.imageRect.offset.x),
    static_cast<GLint>(layerView.subImage.imageRect.offset.y),
    static_cast<GLsizei>(layerView.subImage.imageRect.extent.width),
    static_cast<GLsizei>(layerView.subImage.imageRect.extent.height));

    glFrontFace(GL_CW);
    glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    const uint32_t depthTexture = GetDepthTexture(colorTexture);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

    // Clear swapchain and depth buffer.
    glClearColor(BackgroundColor[0], BackgroundColor[1], BackgroundColor[2], BackgroundColor[3]);
    glClearDepthf(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    if (xrScene) {
    xrScene->RenderFrame(layerView, viewID);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Swap our window every other eye for RenderDoc
    static int everyOther = 0;
    if ((everyOther++ & 1) != 0) {
    ksGpuWindow_SwapBuffers(&window);
    }
    }

  3. 释放视频帧:绘制完成后,需要调用MTXRReleaseFrame通知MTVerseXR SDK内部释放和回收视频资源。

    void XRScene::EndFrame() {
    MTXRReleaseFrame(m_receiver, m_xrFrame.get());
    m_xrFrame = nullptr;
    }

4.4 资源清理

退出客户端应用前,需要调用MTXRDestroyReceiver释放MTVerseXR的相关资源。

note

相关安装包、源码可以通过开发者社区下载:MTVerseXR SDK | 摩尔线程开发者)

相关推荐
mirrornan6 小时前
3D和AR技术在电商行业的应用有哪些?
3d·ar·3d建模·3d模型·三维建模
m0_748234348 小时前
webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
图形渲染·webgl
每日出拳老爷子8 小时前
【图形渲染】【Unity Shader】【Nvidia CG】有用的参考资料链接
unity·游戏引擎·图形渲染
工业3D_大熊8 小时前
3D开发工具HOOPS助力造船业加速设计与数字化转型
3d
zaf赵8 小时前
3D 高斯溅射 (Gaussian Splatting)技术,一种实现超写实、高效渲染的突破性技术
3d
www_3dyz_com10 小时前
人工智能在VR展览中扮演什么角色?
人工智能·vr
电摇小人10 小时前
《探索 VR:开启沉浸式虚拟世界之旅》
vr
前端Hardy12 小时前
HTML&CSS:酷炫的3D开关控件
前端·javascript·css·3d·html
Debroon1 天前
M3D: 基于多模态大模型的新型3D医学影像分析框架,将3D医学图像分析从“看图片“提升到“理解空间“的层次,支持检索、报告生成、问答、定位和分割等8类任务
3d
广东数字化转型2 天前
Three.js相机Camera控件知识梳理
3d·three.js