当前位置:   article > 正文

VR——Pico OpenXR开发框架

openxr

一、VR基础知识

1、OpenXR简介

        OpenXR是一个针对XR应用程序接口,简称API。 OpenXR的最终目标是将VR/AR应用和头显之间的通信方式标准化。在2017年,由Khronos Group发起,联合多家行业头部公司一起制定了一个开放标准Open XR。 该标准的目标是为开发人员提供一个简化的方式来创建跨平台VR和AR应用程序和游戏,这些应用程序和游戏可以在支持OpenXR的各种设备上运行。         

        注:最新OpenXR标准规范1.0版于2019年推出,尽管采用缓慢,但一直稳定增长。目前,Meta、Sony、Valve、Microsoft、HTC、NVIDIA 和 AMD 已相继支持该标准(Apple 暂不支持)。Meta还在2021年7月将OpenXR兼容性支持纳入了新发行Quest App的基本要求。 总而言之,OpenXR 是VR/AR领域的一个重要里程碑。这个API将允许游戏和其他应用程序在各种硬件平台上轻松运行,而无需专有的SDK。

2、串流与云VR

①本地VR---在使用本地VR时,VR系统的整个渲染流程包括:
整个过程加起来,行业标准是需要小于6ms
②串流时---额外需要增加编码、解码过程,以及WIFI传输过程。整个流程下来,大约有50ms左右的延迟
③云VR---有个网络传送,所有云VR要比串流延迟再增加20Ms以上

3、总结

        把一个有透镜成像功能的显示器罩在眼睛上,人向哪里看,就在显示器里显示对应方向的景物画面,从而让人感觉自己身处一个无限大的虚拟空间中。
  • 处理器 :即计算的核心,用来计算和生成图像,并根据陀螺仪数据计算你的姿态定位等
  • 显示器 :分别向左右眼睛显示图像。一般当我们说 2k 屏幕的VR眼镜时,是指一整块屏幕的长边的尺寸,比如 2k*1k 尺寸。但如果说:单眼2k,则是指屏幕短边的尺寸是2k,双眼则是 4K
  • 透镜 :通过折射光线,将显示器上的画面成像拉近到视网膜位置,使人的眼睛能轻松看清几乎贴在眼前的显示屏
  • 陀螺仪 :它是能检测到物体在空间中的姿态 / 朝向的传感器。在 VR 显示器里的景象,如果要随着人头部的运动而实时产生变化,则必须知道人头部的朝向。

二、Pico OpenXR开发框架

1、Pico+OpenXR开发环境搭建关键点

① 创建一个3D URP项目(效果适中,HDRP适合大团队),将开发环境切换至Android,Pico 一体机VR搭载是安卓系统。

②安装PICO Unity Integration SDK 、XR Interaction ToolkitPICO Unity OpenXR SDK。

首先按照官网配置完成PICO Developer

XR Interaction Toolkit:打开Packages Manage中搜索XR Interaction Toolkit并安装

PICO Unity Integration SDK:在官网中直接下载SDK进行安装,补全开发中针对PICO一体机的关键XR能力

Pico OpenXR SDK:在官网中直接下载SDK进行安装

如遇安装过程异常:如果直接安装Pico OpenXR Plugin SDK后并没有自动安装OpenXR SDK(Unity原生),则应卸载Pico OpenXR Plugin SDK,然后先安装OpenXR SDK(Unity原生)再安装Pico OpenXR Plugin SDK,否则项目依旧不支持PICO,没有进行相关链接。

注:目前Pico OpenXR SDK处于Preview版,暂时无法用于商业开发,使用该插件开发的应用将无法通过商店审核。只能用于个人开发者尝鲜学习使用。

③烧入后发现手柄连接不上,需要另外修改Pico openxr plugin代码,具体如下:
在最新的Unity OpenXR Plugins中UnityEngine.XR.OpenXR.Input.PoseControl类型已经被废弃,需使用 UnityEngine.InputSystem.XR.PoseControl代替。

④打开Packages Manage中安卓AndroidLogcat,方便后续直接接入PicoVR一体机进行调试,使用adb进行软件安装(具体的adb简单使用在我的博客VR——Oculus篇会提到),但效率太慢。这时候就有人问我,为什么不用PICO Unity Live Preview Plugin 串流调试,我的回答是这个SDK不太稳定,针对不同的PC系统/配置还存在Bug,换台电脑可能连通讯都通不上。

2、通用OpenXR能力——OpenXR代码实现射线检测逻辑-射线进入、射线点击、射线退出

①增添左右射线控制器

  1. [Tooltip("玩家左射线控制")] public XRRayInteractor RayInteractor_Left;
  2. [Tooltip("玩家右射线控制")] public XRRayInteractor RayInteractor_Right;
  3. private InputDevice curLeftController, curRightController;
  4. private GameObject curLeftGameObject, lastLeftGameObject;
  5. private GameObject curRightGameObject, lastRightGameObject;
  6. private RaycastHit rayInfoLeft, rayInfoRight;
  7. private bool isEnterLeftPrimaryButton = false, isEnterRightPrimaryButton;
  8. private Dictionary<string, bool> vrButtonStateDic;

②增添射线检测接口

  1. using UnityEngine.XR.Interaction.Toolkit;
  2. public interface IRayPointCheck
  3. {
  4. /// <summary>
  5. /// 射线进入
  6. /// </summary>
  7. public void OnRayEnter(HoverEnterEventArgs arg0);
  8. /// <summary>
  9. /// 射线退出
  10. /// </summary>
  11. public void OnRayExit(HoverExitEventArgs arg0);
  12. /// <summary>
  13. /// 射线点击
  14. /// </summary>
  15. public void OnRayClick();
  16. }

③根据左右手柄对于碰撞物体的检测状态进行判定射线进入、点击及退出逻辑

  1. /// <summary>
  2. /// 射线进入控制
  3. /// </summary>
  4. /// <param name="curObj"></param>
  5. private void RayEnterControl(GameObject curObj)
  6. {
  7. //射线进入逻辑
  8. }
  9. /// <summary>
  10. /// 射线点击控制
  11. /// </summary>
  12. /// <param name="curObj"></param>
  13. private void RayStayClickControl(GameObject curObj)
  14. {
  15. //射线点击逻辑
  16. }
  17. /// <summary>
  18. /// 射线退出控制
  19. /// </summary>
  20. /// <param name="lastObj"></param>
  21. private void RayExitControl(GameObject lastObj)
  22. {
  23. //射线退出逻辑
  24. }
  25. private bool isRayStay = false;
  26. private bool isRayLeftStay = false;
  27. /// <summary>
  28. /// 获取左手柄对3D的射线碰撞信息
  29. /// </summary>
  30. /// <returns></returns>
  31. private void GettLeftRayInfo()
  32. {
  33. if(RayInteractor_Left.TryGetCurrent3DRaycastHit(out rayInfoLeft))
  34. {
  35. curLeftGameObject = rayInfoLeft.collider.gameObject;
  36. }
  37. else
  38. {
  39. curLeftGameObject = null;
  40. }
  41. //射线进入
  42. if(curLeftGameObject != null && lastLeftGameObject == null)
  43. {
  44. isRayLeftStay = false;
  45. RayEnterControl(curLeftGameObject);
  46. lastLeftGameObject = curLeftGameObject;
  47. }
  48. //射线停留点击
  49. if (curLeftGameObject != null && lastLeftGameObject != null && curLeftGameObject == lastLeftGameObject)
  50. {
  51. isRayLeftStay = true;
  52. RayStayClickControl(curLeftGameObject);
  53. lastLeftGameObject = curLeftGameObject;
  54. }
  55. else //射线退出
  56. {
  57. isRayLeftStay = false;
  58. curLeftGameObject = null;
  59. if(lastLeftGameObject != null)
  60. {
  61. RayExitControl(lastLeftGameObject);
  62. }
  63. lastLeftGameObject = null;
  64. }
  65. }
  66. /// <summary>
  67. /// 获取右手柄对3D的射线碰撞信息
  68. /// </summary>
  69. /// <returns></returns>
  70. private void GetRightRayInfo()
  71. {
  72. if (RayInteractor_Right.TryGetCurrent3DRaycastHit(out rayInfoRight))
  73. {
  74. curRightGameObject = rayInfoRight.collider.gameObject;
  75. }
  76. else
  77. {
  78. curRightGameObject = null;
  79. }
  80. //射线进入
  81. if (curRightGameObject != null && lastRightGameObject == null)
  82. {
  83. isRayStay = false;
  84. RayEnterControl(curRightGameObject);
  85. lastRightGameObject = curRightGameObject;
  86. }
  87. //射线点击
  88. if (curRightGameObject != null && lastRightGameObject != null && curRightGameObject == lastRightGameObject)
  89. {
  90. isRayStay = true;
  91. RayStayClickControl(curRightGameObject);
  92. lastRightGameObject = curRightGameObject;
  93. }
  94. else //射线退出
  95. {
  96. isRayStay = false;
  97. curRightGameObject = null;
  98. if (lastRightGameObject != null)
  99. {
  100. RayExitControl(lastRightGameObject);
  101. }
  102. lastRightGameObject = null;
  103. }
  104. }

3、通用OpenXR能力——OpenXR代码实现射线检测逻辑-手柄自定义按键

Pico手柄按键映射示意:手柄&头戴输入映射 | PICO 开发者平台

①初始化手柄左右控制器

  1. /// <summary>
  2. /// 重新初始化
  3. /// </summary>
  4. private void InitControler()
  5. {
  6. if (!curLeftController.isValid)
  7. {
  8. curLeftController = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
  9. }
  10. if (!curRightController.isValid)
  11. {
  12. curRightController = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
  13. }
  14. }

②针对手柄通用4个按键注册按键进入、按下及退出事件

  1. private void ChangeLeftPrimaryButtonState()
  2. {
  3. if (isRayLeftStay)
  4. {
  5. isEnterLeftPrimaryButton = true;
  6. }
  7. }
  8. private void ChangeRightPrimaryButtonState()
  9. {
  10. if (isRayStay)
  11. {
  12. isEnterRightPrimaryButton = true;
  13. }
  14. }
  15. /// <summary>
  16. ///按键检测函数
  17. /// </summary>
  18. /// <param name="device"></param>
  19. /// <param name="usage"></param>
  20. /// <param name="buttonEnter"></param>
  21. /// <param name="buttonDown"></param>
  22. /// <param name="buttonUp"></param>
  23. private void ButtonDispatch(InputDevice device,InputFeatureUsage<bool> usage,Action buttonEnter,Action buttonDown,Action buttonUp)
  24. {
  25. //必须这个名称,里面包含device信息
  26. string featureKey = device.characteristics + usage.name;
  27. //string featureKey = usage.name;
  28. if (!vrButtonStateDic.ContainsKey(featureKey))
  29. {
  30. vrButtonStateDic.Add(featureKey, false);
  31. }
  32. bool isDown;
  33. if(device.TryGetFeatureValue(usage,out isDown) && isDown)
  34. {
  35. //Enter按键按下只执行一次
  36. if (!vrButtonStateDic[featureKey])
  37. {
  38. Debug.Log(featureKey + "---Button Enter");
  39. vrButtonStateDic[featureKey] = true;
  40. buttonEnter?.Invoke();
  41. }
  42. //Down在按键按下会执行多次,主要还是帧时间不适配
  43. Debug.Log(featureKey + "--Button Down");
  44. buttonDown?.Invoke();
  45. }
  46. else
  47. {
  48. //Enter按键按下只执行一次
  49. if (vrButtonStateDic[featureKey])
  50. {
  51. Debug.Log(featureKey + "--Button Up");
  52. buttonUp?.Invoke();
  53. vrButtonStateDic[featureKey] = false;
  54. }
  55. }
  56. }
  57. /// <summary>
  58. /// 获取手柄输入信息
  59. /// </summary>
  60. private void GetControllerStatus()
  61. {
  62. if (curLeftController.isValid)
  63. {
  64. ButtonDispatch(curLeftController, CommonUsages.primaryButton, onLeftPrimaryEnter, onLeftPrimaryDown, onLeftPrimaryUp);
  65. ButtonDispatch(curLeftController, CommonUsages.secondaryButton, onLeftSecondaryEnter, onLeftSecondaryDown, onLeftSecondaryUp);
  66. ButtonDispatch(curLeftController, CommonUsages.triggerButton, onLeftTriggerEnter, onLeftTriggerDown, onLeftTriggerUp);
  67. ButtonDispatch(curLeftController, CommonUsages.gripButton, onLeftGripEnter, onLeftGripDown, onLeftGripUp);
  68. }
  69. else
  70. {
  71. InitControler();
  72. }
  73. if (curRightController.isValid)
  74. {
  75. ButtonDispatch(curRightController, CommonUsages.primaryButton, onRightPrimaryEnter, onRightPrimaryDown, onRightPrimaryUp);
  76. ButtonDispatch(curRightController, CommonUsages.secondaryButton, onRightSecondaryEnter, onRightSecondaryDown, onRightSecondaryUp);
  77. ButtonDispatch(curRightController, CommonUsages.triggerButton, onRightTriggerEnter, onRightTriggerDown, onRightTriggerUp);
  78. ButtonDispatch(curRightController, CommonUsages.gripButton, onRightGripEnter, onRightGripDown, onRightGripUp);
  79. }
  80. else
  81. {
  82. InitControler();
  83. }
  84. }

③测试代码,可参考以下

  1. if (primaryButton_A)
  2. {
  3. Text.text = "ALeft";
  4. SetHapticImpulse_Left();
  5. }
  6. else if (secondaryButton_B)
  7. {
  8. Text.text = "BLeft";
  9. }
  10. else if (triggerButton_Left)
  11. {
  12. Text.text = "triggerLeft";
  13. }
  14. else if (gripButton_Left)
  15. {
  16. Text.text = "gripButtonLeft";
  17. }
  18. if (primaryButton_X)
  19. {
  20. Text.text = "ARight";
  21. SetHapticImpulse_Right();
  22. }
  23. else if (secondaryButton_Y)
  24. {
  25. Text.text = "BRight";
  26. }
  27. else if (triggerButton_Right)
  28. {
  29. Text.text = "triggerRight";
  30. }
  31. else if (gripButton_Right)
  32. {
  33. Text.text = "gripButtonRight";
  34. }

4、通用OpenXR能力——OpenXR代码实现射线检测逻辑-震动反馈

①注册左右手柄的震动事件,我这边是用我写的事件框架里添加,读者可以使用Action自定义添加手柄震动事件,注意好震动Api的三个形参就行

  1. //震动事件
  2. EventManager.AddEventListener<float, int, int>("TurnOnHaptics", SetHapticImpulse_Left);
  3. EventManager.AddEventListener<float, int, int>("TurnOnHaptics", SetHapticImpulse_Right);

②调用XR封装好的震动API

  1. /// <summary>
  2. /// 左手柄触发震动
  3. /// 默认amplitude:0.5f,duration : 500,frequency : 100
  4. /// </summary>
  5. private void SetHapticImpulse_Left(float amplitude, int duration, int frequency = 150)
  6. {
  7. PXR_Input.SendHapticImpulse(PXR_Input.VibrateType.LeftController, amplitude, duration, frequency);
  8. }
  9. /// <summary>
  10. /// 右手柄触发震动
  11. /// </summary>
  12. private void SetHapticImpulse_Right(float amplitude, int duration, int frequency = 150)
  13. {
  14. PXR_Input.SendHapticImpulse(PXR_Input.VibrateType.RightController, amplitude, duration, frequency);
  15. }

5、Pico开发交互注意点

②UI界面交互注意点:
  • 需在XR幕布下才能正常触发UI点击事件
  • 在对应控件需添加TrackedDeviceGraphicRaycaster脚本,便可通过射线触发
③三维物体交互注意点:
  • 需在对应物体下添加XRSimpleInteractable脚本,用代码控制便可通过射线触发,Hover-射线碰撞,Select Entered-射线点击
④可添加XR DEviceSimulator模拟Pico手柄,需通过PackageManager的XR Interaction Toolkit添加Samples
⑤添加PokeInteractor和Tracked Pose Driver脚本在某个控制器下面--作为手柄碰撞检测
具体交互按钮需要添加XR Poke Follow Affordance脚本才能生效触碰点击
⑥视线交互-利用XR Gaze Interactor和Gaze Input Manager来实现视线检测(分头部姿势检测以及眼球检测),视线检测只提供事件状态,相当于一条射线。
注意:被检测对象需添加collider碰撞体及XR Simple Interactable,具体交互逻辑通过XR Simple Interactable视线。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/468015
推荐阅读
  

闽ICP备14008679号