当前位置:   article > 正文

XR和Steam VR项目合并问题

XR和Steam VR项目合并问题

最近有一个项目是用Steam VR开发的,里面部分场景是用VRTK框架做的,还有一部分是用SteamVR SDK自带的Player预制直接开发的。

这样本身没有问题,因为最终都是通过SteamVR SDK处理的,VRTK也管理好了SteamVR的逻辑,并且支持动态切换,比如切换成Oculus的。

然后现在遇到一个问题,还有一个项目是用Unity自带的XR开发的,Package Manager导入XR相关的插件实现的。

 

需要将XR开发的项目移植到Steam VR项目来,然后事情就开始了。

SteamVR的场景可以运行,通过Pico以及Quest串流还有htc头盔都能正常识别,手柄也能控制。

但是XR场景就出现问题了,头盔无法识别。

经过一步步排查,发现是XR Plug-in Management这里需要设置不同的XRLoader。

而SteamVR是OpenVR Loader,而XR是OpenXR,因为OpenVR Loader在前,所以激活的是OpenVR Loader,这也是为什么SteamVR场景可以运行而XR场景不行。

我们看看unity的源代码是怎么写的,发现这里面是有activeLoader的概念,也就是一次只能一个Loader运行。

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Runtime.CompilerServices;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using UnityEngine.Rendering;
  9. using UnityEngine.UIElements;
  10. using UnityEngine.Serialization;
  11. using UnityEngine.XR.Management;
  12. [assembly: InternalsVisibleTo("Unity.XR.Management.Tests")]
  13. [assembly: InternalsVisibleTo("Unity.XR.Management.EditorTests")]
  14. namespace UnityEngine.XR.Management
  15. {
  16. /// <summary>
  17. /// Class to handle active loader and subsystem management for XR. This class is to be added as a
  18. /// ScriptableObject asset in your project and should only be referenced by the <see cref="XRGeneralSettings"/>
  19. /// instance for its use.
  20. ///
  21. /// Given a list of loaders, it will attempt to load each loader in the given order. The first
  22. /// loader that is successful wins and all remaining loaders are ignored. The loader
  23. /// that succeeds is accessible through the <see cref="activeLoader"/> property on the manager.
  24. ///
  25. /// Depending on configuration the <see cref="XRManagerSettings"/> instance will automatically manage the active loader
  26. /// at correct points in the application lifecycle. The user can override certain points in the active loader lifecycle
  27. /// and manually manage them by toggling the <see cref="automaticLoading"/> and <see cref="automaticRunning"/>
  28. /// properties. Disabling <see cref="automaticLoading"/> implies the the user is responsible for the full lifecycle
  29. /// of the XR session normally handled by the <see cref="XRManagerSettings"/> instance. Toggling this to false also toggles
  30. /// <see cref="automaticRunning"/> false.
  31. ///
  32. /// Disabling <see cref="automaticRunning"/> only implies that the user is responsible for starting and stopping
  33. /// the <see cref="activeLoader"/> through the <see cref="StartSubsystems"/> and <see cref="StopSubsystems"/> APIs.
  34. ///
  35. /// Automatic lifecycle management is executed as follows
  36. ///
  37. /// * Runtime Initialize -> <see cref="InitializeLoader"/>. The loader list will be iterated over and the first successful loader will be set as the active loader.
  38. /// * Start -> <see cref="StartSubsystems"/>. Ask the active loader to start all subsystems.
  39. /// * OnDisable -> <see cref="StopSubsystems"/>. Ask the active loader to stop all subsystems.
  40. /// * OnDestroy -> <see cref="DeinitializeLoader"/>. Deinitialize and remove the active loader.
  41. /// </summary>
  42. public sealed class XRManagerSettings : ScriptableObject
  43. {
  44. [HideInInspector]
  45. bool m_InitializationComplete = false;
  46. #pragma warning disable 414
  47. // This property is only used by the scriptable object editing part of the system and as such no one
  48. // directly references it. Have to manually disable the console warning here so that we can
  49. // get a clean console report.
  50. [HideInInspector]
  51. [SerializeField]
  52. bool m_RequiresSettingsUpdate = false;
  53. #pragma warning restore 414
  54. [SerializeField]
  55. [Tooltip("Determines if the XR Manager instance is responsible for creating and destroying the appropriate loader instance.")]
  56. [FormerlySerializedAs("AutomaticLoading")]
  57. bool m_AutomaticLoading = false;
  58. /// <summary>
  59. /// Get and set Automatic Loading state for this manager. When this is true, the manager will automatically call
  60. /// <see cref="InitializeLoader"/> and <see cref="DeinitializeLoader"/> for you. When false <see cref="automaticRunning"/>
  61. /// is also set to false and remains that way. This means that disabling automatic loading disables all automatic behavior
  62. /// for the manager.
  63. /// </summary>
  64. public bool automaticLoading
  65. {
  66. get { return m_AutomaticLoading; }
  67. set { m_AutomaticLoading = value; }
  68. }
  69. [SerializeField]
  70. [Tooltip("Determines if the XR Manager instance is responsible for starting and stopping subsystems for the active loader instance.")]
  71. [FormerlySerializedAs("AutomaticRunning")]
  72. bool m_AutomaticRunning = false;
  73. /// <summary>
  74. /// Get and set automatic running state for this manager. When set to true the manager will call <see cref="StartSubsystems"/>
  75. /// and <see cref="StopSubsystems"/> APIs at appropriate times. When set to false, or when <see cref="automaticLoading"/> is false
  76. /// then it is up to the user of the manager to handle that same functionality.
  77. /// </summary>
  78. public bool automaticRunning
  79. {
  80. get { return m_AutomaticRunning; }
  81. set { m_AutomaticRunning = value; }
  82. }
  83. [SerializeField]
  84. [Tooltip("List of XR Loader instances arranged in desired load order.")]
  85. [FormerlySerializedAs("Loaders")]
  86. List<XRLoader> m_Loaders = new List<XRLoader>();
  87. // Maintains a list of registered loaders that is immutable at runtime.
  88. [SerializeField]
  89. [HideInInspector]
  90. HashSet<XRLoader> m_RegisteredLoaders = new HashSet<XRLoader>();
  91. /// <summary>
  92. /// List of loaders currently managed by this XR Manager instance.
  93. /// </summary>
  94. /// <remarks>
  95. /// Modifying the list of loaders at runtime is undefined behavior and could result in a crash or memory leak.
  96. /// Use <see cref="activeLoaders"/> to retrieve the currently ordered list of loaders. If you need to mutate
  97. /// the list at runtime, use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and
  98. /// <see cref="TrySetLoaders"/>.
  99. /// </remarks>
  100. [Obsolete("'XRManagerSettings.loaders' property is obsolete. Use 'XRManagerSettings.activeLoaders' instead to get a list of the current loaders.")]
  101. public List<XRLoader> loaders
  102. {
  103. get { return m_Loaders; }
  104. #if UNITY_EDITOR
  105. set { m_Loaders = value; }
  106. #endif
  107. }
  108. /// <summary>
  109. /// A shallow copy of the list of loaders currently managed by this XR Manager instance.
  110. /// </summary>
  111. /// <remarks>
  112. /// This property returns a read only list. Any changes made to the list itself will not affect the list
  113. /// used by this XR Manager instance. To mutate the list of loaders currently managed by this instance,
  114. /// use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and/or <see cref="TrySetLoaders"/>.
  115. /// </remarks>
  116. public IReadOnlyList<XRLoader> activeLoaders => m_Loaders;
  117. /// <summary>
  118. /// Read only boolean letting us know if initialization is completed. Because initialization is
  119. /// handled as a Coroutine, people taking advantage of the auto-lifecycle management of XRManager
  120. /// will need to wait for init to complete before checking for an ActiveLoader and calling StartSubsystems.
  121. /// </summary>
  122. public bool isInitializationComplete
  123. {
  124. get { return m_InitializationComplete; }
  125. }
  126. ///<summary>
  127. /// Return the current singleton active loader instance.
  128. ///</summary>
  129. [HideInInspector]
  130. public XRLoader activeLoader { get; private set; }
  131. /// <summary>
  132. /// Return the current active loader, cast to the requested type. Useful shortcut when you need
  133. /// to get the active loader as something less generic than XRLoader.
  134. /// </summary>
  135. ///
  136. /// <typeparam name="T">Requested type of the loader</typeparam>
  137. ///
  138. /// <returns>The active loader as requested type, or null.</returns>
  139. public T ActiveLoaderAs<T>() where T : XRLoader
  140. {
  141. return activeLoader as T;
  142. }
  143. /// <summary>
  144. /// Iterate over the configured list of loaders and attempt to initialize each one. The first one
  145. /// that succeeds is set as the active loader and initialization immediately terminates.
  146. ///
  147. /// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
  148. /// call other parts of the API. This does not guarantee that init successfully created a loader. For that
  149. /// you need to check that ActiveLoader is not null.
  150. ///
  151. /// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
  152. /// already set will cause a warning to be logged and immediate exit of this function.
  153. ///
  154. /// This method is synchronous and on return all state should be immediately checkable.
  155. ///
  156. /// <b>If manual initialization of XR is being done, this method can not be called before Start completes
  157. /// as it depends on graphics initialization within Unity completing.</b>
  158. /// </summary>
  159. public void InitializeLoaderSync()
  160. {
  161. if (activeLoader != null)
  162. {
  163. Debug.LogWarning(
  164. "XR Management has already initialized an active loader in this scene." +
  165. " Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
  166. return;
  167. }
  168. foreach (var loader in currentLoaders)
  169. {
  170. if (loader != null)
  171. {
  172. if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
  173. {
  174. activeLoader = loader;
  175. m_InitializationComplete = true;
  176. return;
  177. }
  178. }
  179. }
  180. activeLoader = null;
  181. }
  182. /// <summary>
  183. /// Iterate over the configured list of loaders and attempt to initialize each one. The first one
  184. /// that succeeds is set as the active loader and initialization immediately terminates.
  185. ///
  186. /// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
  187. /// call other parts of the API. This does not guarantee that init successfully created a loader. For that
  188. /// you need to check that ActiveLoader is not null.
  189. ///
  190. /// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
  191. /// already set will cause a warning to be logged and immediate exit of this function.
  192. ///
  193. /// Iteration is done asynchronously and this method must be called within the context of a Coroutine.
  194. ///
  195. /// <b>If manual initialization of XR is being done, this method can not be called before Start completes
  196. /// as it depends on graphics initialization within Unity completing.</b>
  197. /// </summary>
  198. ///
  199. /// <returns>Enumerator marking the next spot to continue execution at.</returns>
  200. public IEnumerator InitializeLoader()
  201. {
  202. if (activeLoader != null)
  203. {
  204. Debug.LogWarning(
  205. "XR Management has already initialized an active loader in this scene." +
  206. " Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
  207. yield break;
  208. }
  209. foreach (var loader in currentLoaders)
  210. {
  211. if (loader != null)
  212. {
  213. if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
  214. {
  215. activeLoader = loader;
  216. m_InitializationComplete = true;
  217. yield break;
  218. }
  219. }
  220. yield return null;
  221. }
  222. activeLoader = null;
  223. }
  224. /// <summary>
  225. /// Attempts to append the given loader to the list of loaders at the given index.
  226. /// </summary>
  227. /// <param name="loader">
  228. /// The <see cref="XRLoader"/> to be added to this manager's instance of loaders.
  229. /// </param>
  230. /// <param name="index">
  231. /// The index at which the given <see cref="XRLoader"/> should be added. If you set a negative or otherwise
  232. /// invalid index, the loader will be appended to the end of the list.
  233. /// </param>
  234. /// <returns>
  235. /// <c>true</c> if the loader is not a duplicate and was added to the list successfully, <c>false</c>
  236. /// otherwise.
  237. /// </returns>
  238. /// <remarks>
  239. /// This method behaves differently in the Editor and during runtime/Play mode. While your app runs in the Editor and not in
  240. /// Play mode, attempting to add an <see cref="XRLoader"/> will always succeed and register that loader's type
  241. /// internally. Attempting to add a loader during runtime/Play mode will trigger a check to see whether a loader of
  242. /// that type was registered. If the check is successful, the loader is added. If not, the loader is not added and the method
  243. /// returns <c>false</c>.
  244. /// </remarks>
  245. public bool TryAddLoader(XRLoader loader, int index = -1)
  246. {
  247. if (loader == null || currentLoaders.Contains(loader))
  248. return false;
  249. #if UNITY_EDITOR
  250. if (!EditorApplication.isPlaying && !m_RegisteredLoaders.Contains(loader))
  251. m_RegisteredLoaders.Add(loader);
  252. #endif
  253. if (!m_RegisteredLoaders.Contains(loader))
  254. return false;
  255. if (index < 0 || index >= currentLoaders.Count)
  256. currentLoaders.Add(loader);
  257. else
  258. currentLoaders.Insert(index, loader);
  259. return true;
  260. }
  261. /// <summary>
  262. /// Attempts to remove the first instance of a given loader from the list of loaders.
  263. /// </summary>
  264. /// <param name="loader">
  265. /// The <see cref="XRLoader"/> to be removed from this manager's instance of loaders.
  266. /// </param>
  267. /// <returns>
  268. /// <c>true</c> if the loader was successfully removed from the list, <c>false</c> otherwise.
  269. /// </returns>
  270. /// <remarks>
  271. /// This method behaves differently in the Editor and during runtime/Play mode. During runtime/Play mode, the loader
  272. /// will be removed with no additional side effects if it is in the list managed by this instance. While in the
  273. /// Editor and not in Play mode, the loader will be removed if it exists and
  274. /// it will be unregistered from this instance and any attempts to add it during
  275. /// runtime/Play mode will fail. You can re-add the loader in the Editor while not in Play mode.
  276. /// </remarks>
  277. public bool TryRemoveLoader(XRLoader loader)
  278. {
  279. var removedLoader = true;
  280. if (currentLoaders.Contains(loader))
  281. removedLoader = currentLoaders.Remove(loader);
  282. #if UNITY_EDITOR
  283. if (!EditorApplication.isPlaying && !currentLoaders.Contains(loader))
  284. m_RegisteredLoaders.Remove(loader);
  285. #endif
  286. return removedLoader;
  287. }
  288. /// <summary>
  289. /// Attempts to set the given loader list as the list of loaders managed by this instance.
  290. /// </summary>
  291. /// <param name="reorderedLoaders">
  292. /// The list of <see cref="XRLoader"/>s to be managed by this manager instance.
  293. /// </param>
  294. /// <returns>
  295. /// <c>true</c> if the loader list was set successfully, <c>false</c> otherwise.
  296. /// </returns>
  297. /// <remarks>
  298. /// This method behaves differently in the Editor and during runtime/Play mode. While in the Editor and not in
  299. /// Play mode, any attempts to set the list of loaders will succeed without any additional checks. During
  300. /// runtime/Play mode, the new loader list will be validated against the registered <see cref="XRLoader"/> types.
  301. /// If any loaders exist in the list that were not registered at startup, the attempt will fail.
  302. /// </remarks>
  303. public bool TrySetLoaders(List<XRLoader> reorderedLoaders)
  304. {
  305. var originalLoaders = new List<XRLoader>(activeLoaders);
  306. #if UNITY_EDITOR
  307. if (!EditorApplication.isPlaying)
  308. {
  309. registeredLoaders.Clear();
  310. currentLoaders.Clear();
  311. foreach (var loader in reorderedLoaders)
  312. {
  313. if (!TryAddLoader(loader))
  314. {
  315. TrySetLoaders(originalLoaders);
  316. return false;
  317. }
  318. }
  319. return true;
  320. }
  321. #endif
  322. currentLoaders.Clear();
  323. foreach (var loader in reorderedLoaders)
  324. {
  325. if (!TryAddLoader(loader))
  326. {
  327. currentLoaders = originalLoaders;
  328. return false;
  329. }
  330. }
  331. return true;
  332. }
  333. private bool CheckGraphicsAPICompatibility(XRLoader loader)
  334. {
  335. GraphicsDeviceType deviceType = SystemInfo.graphicsDeviceType;
  336. List<GraphicsDeviceType> supportedDeviceTypes = loader.GetSupportedGraphicsDeviceTypes(false);
  337. // To help with backward compatibility, if the compatibility list is empty we assume that it does not implement the GetSupportedGraphicsDeviceTypes method
  338. // Therefore we revert to the previous behavior of building or starting the loader regardless of gfx api settings.
  339. if (supportedDeviceTypes.Count > 0 && !supportedDeviceTypes.Contains(deviceType))
  340. {
  341. Debug.LogWarning(String.Format("The {0} does not support the initialized graphics device, {1}. Please change the preffered Graphics API in PlayerSettings. Attempting to start the next XR loader.", loader.name, deviceType.ToString()));
  342. return false;
  343. }
  344. return true;
  345. }
  346. /// <summary>
  347. /// If there is an active loader, this will request the loader to start all the subsystems that it
  348. /// is managing.
  349. ///
  350. /// You must wait for <see cref="isInitializationComplete"/> to be set to true prior to calling this API.
  351. /// </summary>
  352. public void StartSubsystems()
  353. {
  354. if (!m_InitializationComplete)
  355. {
  356. Debug.LogWarning(
  357. "Call to StartSubsystems without an initialized manager." +
  358. "Please make sure wait for initialization to complete before calling this API.");
  359. return;
  360. }
  361. if (activeLoader != null)
  362. {
  363. activeLoader.Start();
  364. }
  365. }
  366. /// <summary>
  367. /// If there is an active loader, this will request the loader to stop all the subsystems that it
  368. /// is managing.
  369. ///
  370. /// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
  371. /// </summary>
  372. public void StopSubsystems()
  373. {
  374. if (!m_InitializationComplete)
  375. {
  376. Debug.LogWarning(
  377. "Call to StopSubsystems without an initialized manager." +
  378. "Please make sure wait for initialization to complete before calling this API.");
  379. return;
  380. }
  381. if (activeLoader != null)
  382. {
  383. activeLoader.Stop();
  384. }
  385. }
  386. /// <summary>
  387. /// If there is an active loader, this function will deinitialize it and remove the active loader instance from
  388. /// management. We will automatically call <see cref="StopSubsystems"/> prior to deinitialization to make sure
  389. /// that things are cleaned up appropriately.
  390. ///
  391. /// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
  392. ///
  393. /// Upon return <see cref="isInitializationComplete"/> will be rest to false;
  394. /// </summary>
  395. public void DeinitializeLoader()
  396. {
  397. if (!m_InitializationComplete)
  398. {
  399. Debug.LogWarning(
  400. "Call to DeinitializeLoader without an initialized manager." +
  401. "Please make sure wait for initialization to complete before calling this API.");
  402. return;
  403. }
  404. StopSubsystems();
  405. if (activeLoader != null)
  406. {
  407. activeLoader.Deinitialize();
  408. activeLoader = null;
  409. }
  410. m_InitializationComplete = false;
  411. }
  412. // Use this for initialization
  413. void Start()
  414. {
  415. if (automaticLoading && automaticRunning)
  416. {
  417. StartSubsystems();
  418. }
  419. }
  420. void OnDisable()
  421. {
  422. if (automaticLoading && automaticRunning)
  423. {
  424. StopSubsystems();
  425. }
  426. }
  427. void OnDestroy()
  428. {
  429. if (automaticLoading)
  430. {
  431. DeinitializeLoader();
  432. }
  433. }
  434. // To modify the list of loaders internally use `currentLoaders` as it will return a list reference rather
  435. // than a shallow copy.
  436. // TODO @davidmo 10/12/2020: remove this in next major version bump and make 'loaders' internal.
  437. internal List<XRLoader> currentLoaders
  438. {
  439. get { return m_Loaders; }
  440. set { m_Loaders = value; }
  441. }
  442. // To modify the set of registered loaders use `registeredLoaders` as it will return a reference to the
  443. // hashset of loaders.
  444. internal HashSet<XRLoader> registeredLoaders
  445. {
  446. get { return m_RegisteredLoaders; }
  447. }
  448. }
  449. }

事情变的有趣起来,我们知道了这样的原理之后,那鱼蛋我就想着尝试下,在Runtime里动态切换行吧,SteamVR场景切换到OpenVR Loader,而XR场景切换到OpenXR,代码如下。

  1. using System.Collections.Generic;
  2. using Unity.XR.OpenVR;
  3. using UnityEngine;
  4. using UnityEngine.XR.Management;
  5. using UnityEngine.XR.OpenXR;
  6. namespace EgoGame
  7. {
  8. /// <summary>
  9. /// 该类有问题,废弃了
  10. /// </summary>
  11. public class AutoXRLoader:MonoBehaviour
  12. {
  13. public List<XRLoader> xrLoaders;
  14. public List<XRLoader> vrLoaders;
  15. public bool isXR;
  16. private void Awake()
  17. {
  18. SetLoader(isXR);
  19. }
  20. private void OnDestroy()
  21. {
  22. SetLoader(!isXR);
  23. }
  24. void SetLoader(bool xr)
  25. {
  26. //不这样,会频繁的退出loader,VR会没画面
  27. if (xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenXRLoader)
  28. {
  29. return;
  30. }
  31. if (!xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenVRLoader)
  32. {
  33. return;
  34. }
  35. var loaders = xr ? xrLoaders : vrLoaders;
  36. Debug.Log("切换Loader:" + xr+"=="+XRGeneralSettings.Instance.Manager.activeLoader);
  37. XRGeneralSettings.Instance.Manager.DeinitializeLoader();
  38. XRGeneralSettings.Instance.Manager.TrySetLoaders(loaders);
  39. XRGeneralSettings.Instance.Manager.InitializeLoaderSync();
  40. XRGeneralSettings.Instance.Manager.StartSubsystems();
  41. }
  42. }
  43. }

果然奏效了,XR场景能在头盔里识别并运行了,手柄也能控制。但是,切到SteamVR场景就出现了问题,Steam VR SDK报错了,报错提示有另一个应用在使用SteamVR。

最后的结果就是,没法实现动态切换XR或VR,如果看到此处的人,有办法请告诉我,我尝试了两天用了各种办法,都没法做到。

最后推荐大家开发VR应用不要直接用SteamVR SDK或XR SDK或Oculus SDK开发,而是用那些集成的插件,如VR Interaction Framework、VRTK等,这样在多个VR设备也能快速部署。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/692585
推荐阅读
相关标签
  

闽ICP备14008679号