赞
踩
最近有一个项目是用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运行。
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.CompilerServices;
-
- using UnityEditor;
-
- using UnityEngine;
- using UnityEngine.Rendering;
- using UnityEngine.UIElements;
- using UnityEngine.Serialization;
- using UnityEngine.XR.Management;
-
- [assembly: InternalsVisibleTo("Unity.XR.Management.Tests")]
- [assembly: InternalsVisibleTo("Unity.XR.Management.EditorTests")]
- namespace UnityEngine.XR.Management
- {
- /// <summary>
- /// Class to handle active loader and subsystem management for XR. This class is to be added as a
- /// ScriptableObject asset in your project and should only be referenced by the <see cref="XRGeneralSettings"/>
- /// instance for its use.
- ///
- /// Given a list of loaders, it will attempt to load each loader in the given order. The first
- /// loader that is successful wins and all remaining loaders are ignored. The loader
- /// that succeeds is accessible through the <see cref="activeLoader"/> property on the manager.
- ///
- /// Depending on configuration the <see cref="XRManagerSettings"/> instance will automatically manage the active loader
- /// at correct points in the application lifecycle. The user can override certain points in the active loader lifecycle
- /// and manually manage them by toggling the <see cref="automaticLoading"/> and <see cref="automaticRunning"/>
- /// properties. Disabling <see cref="automaticLoading"/> implies the the user is responsible for the full lifecycle
- /// of the XR session normally handled by the <see cref="XRManagerSettings"/> instance. Toggling this to false also toggles
- /// <see cref="automaticRunning"/> false.
- ///
- /// Disabling <see cref="automaticRunning"/> only implies that the user is responsible for starting and stopping
- /// the <see cref="activeLoader"/> through the <see cref="StartSubsystems"/> and <see cref="StopSubsystems"/> APIs.
- ///
- /// Automatic lifecycle management is executed as follows
- ///
- /// * Runtime Initialize -> <see cref="InitializeLoader"/>. The loader list will be iterated over and the first successful loader will be set as the active loader.
- /// * Start -> <see cref="StartSubsystems"/>. Ask the active loader to start all subsystems.
- /// * OnDisable -> <see cref="StopSubsystems"/>. Ask the active loader to stop all subsystems.
- /// * OnDestroy -> <see cref="DeinitializeLoader"/>. Deinitialize and remove the active loader.
- /// </summary>
- public sealed class XRManagerSettings : ScriptableObject
- {
- [HideInInspector]
- bool m_InitializationComplete = false;
-
- #pragma warning disable 414
- // This property is only used by the scriptable object editing part of the system and as such no one
- // directly references it. Have to manually disable the console warning here so that we can
- // get a clean console report.
- [HideInInspector]
- [SerializeField]
- bool m_RequiresSettingsUpdate = false;
- #pragma warning restore 414
-
- [SerializeField]
- [Tooltip("Determines if the XR Manager instance is responsible for creating and destroying the appropriate loader instance.")]
- [FormerlySerializedAs("AutomaticLoading")]
- bool m_AutomaticLoading = false;
-
- /// <summary>
- /// Get and set Automatic Loading state for this manager. When this is true, the manager will automatically call
- /// <see cref="InitializeLoader"/> and <see cref="DeinitializeLoader"/> for you. When false <see cref="automaticRunning"/>
- /// is also set to false and remains that way. This means that disabling automatic loading disables all automatic behavior
- /// for the manager.
- /// </summary>
- public bool automaticLoading
- {
- get { return m_AutomaticLoading; }
- set { m_AutomaticLoading = value; }
- }
-
- [SerializeField]
- [Tooltip("Determines if the XR Manager instance is responsible for starting and stopping subsystems for the active loader instance.")]
- [FormerlySerializedAs("AutomaticRunning")]
- bool m_AutomaticRunning = false;
-
- /// <summary>
- /// Get and set automatic running state for this manager. When set to true the manager will call <see cref="StartSubsystems"/>
- /// and <see cref="StopSubsystems"/> APIs at appropriate times. When set to false, or when <see cref="automaticLoading"/> is false
- /// then it is up to the user of the manager to handle that same functionality.
- /// </summary>
- public bool automaticRunning
- {
- get { return m_AutomaticRunning; }
- set { m_AutomaticRunning = value; }
- }
-
-
- [SerializeField]
- [Tooltip("List of XR Loader instances arranged in desired load order.")]
- [FormerlySerializedAs("Loaders")]
- List<XRLoader> m_Loaders = new List<XRLoader>();
-
- // Maintains a list of registered loaders that is immutable at runtime.
- [SerializeField]
- [HideInInspector]
- HashSet<XRLoader> m_RegisteredLoaders = new HashSet<XRLoader>();
-
- /// <summary>
- /// List of loaders currently managed by this XR Manager instance.
- /// </summary>
- /// <remarks>
- /// Modifying the list of loaders at runtime is undefined behavior and could result in a crash or memory leak.
- /// Use <see cref="activeLoaders"/> to retrieve the currently ordered list of loaders. If you need to mutate
- /// the list at runtime, use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and
- /// <see cref="TrySetLoaders"/>.
- /// </remarks>
- [Obsolete("'XRManagerSettings.loaders' property is obsolete. Use 'XRManagerSettings.activeLoaders' instead to get a list of the current loaders.")]
- public List<XRLoader> loaders
- {
- get { return m_Loaders; }
- #if UNITY_EDITOR
- set { m_Loaders = value; }
- #endif
- }
-
- /// <summary>
- /// A shallow copy of the list of loaders currently managed by this XR Manager instance.
- /// </summary>
- /// <remarks>
- /// This property returns a read only list. Any changes made to the list itself will not affect the list
- /// used by this XR Manager instance. To mutate the list of loaders currently managed by this instance,
- /// use <see cref="TryAddLoader"/>, <see cref="TryRemoveLoader"/>, and/or <see cref="TrySetLoaders"/>.
- /// </remarks>
- public IReadOnlyList<XRLoader> activeLoaders => m_Loaders;
-
- /// <summary>
- /// Read only boolean letting us know if initialization is completed. Because initialization is
- /// handled as a Coroutine, people taking advantage of the auto-lifecycle management of XRManager
- /// will need to wait for init to complete before checking for an ActiveLoader and calling StartSubsystems.
- /// </summary>
- public bool isInitializationComplete
- {
- get { return m_InitializationComplete; }
- }
-
- ///<summary>
- /// Return the current singleton active loader instance.
- ///</summary>
- [HideInInspector]
- public XRLoader activeLoader { get; private set; }
-
- /// <summary>
- /// Return the current active loader, cast to the requested type. Useful shortcut when you need
- /// to get the active loader as something less generic than XRLoader.
- /// </summary>
- ///
- /// <typeparam name="T">Requested type of the loader</typeparam>
- ///
- /// <returns>The active loader as requested type, or null.</returns>
- public T ActiveLoaderAs<T>() where T : XRLoader
- {
- return activeLoader as T;
- }
-
- /// <summary>
- /// Iterate over the configured list of loaders and attempt to initialize each one. The first one
- /// that succeeds is set as the active loader and initialization immediately terminates.
- ///
- /// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
- /// call other parts of the API. This does not guarantee that init successfully created a loader. For that
- /// you need to check that ActiveLoader is not null.
- ///
- /// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
- /// already set will cause a warning to be logged and immediate exit of this function.
- ///
- /// This method is synchronous and on return all state should be immediately checkable.
- ///
- /// <b>If manual initialization of XR is being done, this method can not be called before Start completes
- /// as it depends on graphics initialization within Unity completing.</b>
- /// </summary>
- public void InitializeLoaderSync()
- {
- if (activeLoader != null)
- {
- Debug.LogWarning(
- "XR Management has already initialized an active loader in this scene." +
- " Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
- return;
- }
-
- foreach (var loader in currentLoaders)
- {
- if (loader != null)
- {
- if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
- {
- activeLoader = loader;
- m_InitializationComplete = true;
- return;
- }
- }
- }
-
- activeLoader = null;
- }
-
- /// <summary>
- /// Iterate over the configured list of loaders and attempt to initialize each one. The first one
- /// that succeeds is set as the active loader and initialization immediately terminates.
- ///
- /// When complete <see cref="isInitializationComplete"/> will be set to true. This will mark that it is safe to
- /// call other parts of the API. This does not guarantee that init successfully created a loader. For that
- /// you need to check that ActiveLoader is not null.
- ///
- /// Note that there can only be one active loader. Any attempt to initialize a new active loader with one
- /// already set will cause a warning to be logged and immediate exit of this function.
- ///
- /// Iteration is done asynchronously and this method must be called within the context of a Coroutine.
- ///
- /// <b>If manual initialization of XR is being done, this method can not be called before Start completes
- /// as it depends on graphics initialization within Unity completing.</b>
- /// </summary>
- ///
- /// <returns>Enumerator marking the next spot to continue execution at.</returns>
- public IEnumerator InitializeLoader()
- {
- if (activeLoader != null)
- {
- Debug.LogWarning(
- "XR Management has already initialized an active loader in this scene." +
- " Please make sure to stop all subsystems and deinitialize the active loader before initializing a new one.");
- yield break;
- }
-
- foreach (var loader in currentLoaders)
- {
- if (loader != null)
- {
- if (CheckGraphicsAPICompatibility(loader) && loader.Initialize())
- {
- activeLoader = loader;
- m_InitializationComplete = true;
- yield break;
- }
- }
-
- yield return null;
- }
-
- activeLoader = null;
- }
-
- /// <summary>
- /// Attempts to append the given loader to the list of loaders at the given index.
- /// </summary>
- /// <param name="loader">
- /// The <see cref="XRLoader"/> to be added to this manager's instance of loaders.
- /// </param>
- /// <param name="index">
- /// The index at which the given <see cref="XRLoader"/> should be added. If you set a negative or otherwise
- /// invalid index, the loader will be appended to the end of the list.
- /// </param>
- /// <returns>
- /// <c>true</c> if the loader is not a duplicate and was added to the list successfully, <c>false</c>
- /// otherwise.
- /// </returns>
- /// <remarks>
- /// This method behaves differently in the Editor and during runtime/Play mode. While your app runs in the Editor and not in
- /// Play mode, attempting to add an <see cref="XRLoader"/> will always succeed and register that loader's type
- /// internally. Attempting to add a loader during runtime/Play mode will trigger a check to see whether a loader of
- /// that type was registered. If the check is successful, the loader is added. If not, the loader is not added and the method
- /// returns <c>false</c>.
- /// </remarks>
- public bool TryAddLoader(XRLoader loader, int index = -1)
- {
- if (loader == null || currentLoaders.Contains(loader))
- return false;
-
- #if UNITY_EDITOR
- if (!EditorApplication.isPlaying && !m_RegisteredLoaders.Contains(loader))
- m_RegisteredLoaders.Add(loader);
- #endif
- if (!m_RegisteredLoaders.Contains(loader))
- return false;
-
- if (index < 0 || index >= currentLoaders.Count)
- currentLoaders.Add(loader);
- else
- currentLoaders.Insert(index, loader);
-
- return true;
- }
-
- /// <summary>
- /// Attempts to remove the first instance of a given loader from the list of loaders.
- /// </summary>
- /// <param name="loader">
- /// The <see cref="XRLoader"/> to be removed from this manager's instance of loaders.
- /// </param>
- /// <returns>
- /// <c>true</c> if the loader was successfully removed from the list, <c>false</c> otherwise.
- /// </returns>
- /// <remarks>
- /// This method behaves differently in the Editor and during runtime/Play mode. During runtime/Play mode, the loader
- /// will be removed with no additional side effects if it is in the list managed by this instance. While in the
- /// Editor and not in Play mode, the loader will be removed if it exists and
- /// it will be unregistered from this instance and any attempts to add it during
- /// runtime/Play mode will fail. You can re-add the loader in the Editor while not in Play mode.
- /// </remarks>
- public bool TryRemoveLoader(XRLoader loader)
- {
- var removedLoader = true;
- if (currentLoaders.Contains(loader))
- removedLoader = currentLoaders.Remove(loader);
-
- #if UNITY_EDITOR
- if (!EditorApplication.isPlaying && !currentLoaders.Contains(loader))
- m_RegisteredLoaders.Remove(loader);
- #endif
-
- return removedLoader;
- }
-
- /// <summary>
- /// Attempts to set the given loader list as the list of loaders managed by this instance.
- /// </summary>
- /// <param name="reorderedLoaders">
- /// The list of <see cref="XRLoader"/>s to be managed by this manager instance.
- /// </param>
- /// <returns>
- /// <c>true</c> if the loader list was set successfully, <c>false</c> otherwise.
- /// </returns>
- /// <remarks>
- /// This method behaves differently in the Editor and during runtime/Play mode. While in the Editor and not in
- /// Play mode, any attempts to set the list of loaders will succeed without any additional checks. During
- /// runtime/Play mode, the new loader list will be validated against the registered <see cref="XRLoader"/> types.
- /// If any loaders exist in the list that were not registered at startup, the attempt will fail.
- /// </remarks>
- public bool TrySetLoaders(List<XRLoader> reorderedLoaders)
- {
- var originalLoaders = new List<XRLoader>(activeLoaders);
- #if UNITY_EDITOR
- if (!EditorApplication.isPlaying)
- {
- registeredLoaders.Clear();
- currentLoaders.Clear();
- foreach (var loader in reorderedLoaders)
- {
- if (!TryAddLoader(loader))
- {
- TrySetLoaders(originalLoaders);
- return false;
- }
- }
-
- return true;
- }
- #endif
- currentLoaders.Clear();
- foreach (var loader in reorderedLoaders)
- {
- if (!TryAddLoader(loader))
- {
- currentLoaders = originalLoaders;
- return false;
- }
- }
-
- return true;
- }
-
- private bool CheckGraphicsAPICompatibility(XRLoader loader)
- {
- GraphicsDeviceType deviceType = SystemInfo.graphicsDeviceType;
- List<GraphicsDeviceType> supportedDeviceTypes = loader.GetSupportedGraphicsDeviceTypes(false);
-
- // To help with backward compatibility, if the compatibility list is empty we assume that it does not implement the GetSupportedGraphicsDeviceTypes method
- // Therefore we revert to the previous behavior of building or starting the loader regardless of gfx api settings.
- if (supportedDeviceTypes.Count > 0 && !supportedDeviceTypes.Contains(deviceType))
- {
- 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()));
- return false;
- }
-
- return true;
- }
-
- /// <summary>
- /// If there is an active loader, this will request the loader to start all the subsystems that it
- /// is managing.
- ///
- /// You must wait for <see cref="isInitializationComplete"/> to be set to true prior to calling this API.
- /// </summary>
- public void StartSubsystems()
- {
- if (!m_InitializationComplete)
- {
- Debug.LogWarning(
- "Call to StartSubsystems without an initialized manager." +
- "Please make sure wait for initialization to complete before calling this API.");
- return;
- }
-
- if (activeLoader != null)
- {
- activeLoader.Start();
- }
- }
-
- /// <summary>
- /// If there is an active loader, this will request the loader to stop all the subsystems that it
- /// is managing.
- ///
- /// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
- /// </summary>
- public void StopSubsystems()
- {
- if (!m_InitializationComplete)
- {
- Debug.LogWarning(
- "Call to StopSubsystems without an initialized manager." +
- "Please make sure wait for initialization to complete before calling this API.");
- return;
- }
-
- if (activeLoader != null)
- {
- activeLoader.Stop();
- }
- }
-
- /// <summary>
- /// If there is an active loader, this function will deinitialize it and remove the active loader instance from
- /// management. We will automatically call <see cref="StopSubsystems"/> prior to deinitialization to make sure
- /// that things are cleaned up appropriately.
- ///
- /// You must wait for <see cref="isInitializationComplete"/> to be set to tru prior to calling this API.
- ///
- /// Upon return <see cref="isInitializationComplete"/> will be rest to false;
- /// </summary>
- public void DeinitializeLoader()
- {
- if (!m_InitializationComplete)
- {
- Debug.LogWarning(
- "Call to DeinitializeLoader without an initialized manager." +
- "Please make sure wait for initialization to complete before calling this API.");
- return;
- }
-
- StopSubsystems();
- if (activeLoader != null)
- {
- activeLoader.Deinitialize();
- activeLoader = null;
- }
-
- m_InitializationComplete = false;
- }
-
- // Use this for initialization
- void Start()
- {
- if (automaticLoading && automaticRunning)
- {
- StartSubsystems();
- }
- }
-
- void OnDisable()
- {
- if (automaticLoading && automaticRunning)
- {
- StopSubsystems();
- }
- }
-
- void OnDestroy()
- {
- if (automaticLoading)
- {
- DeinitializeLoader();
- }
- }
-
- // To modify the list of loaders internally use `currentLoaders` as it will return a list reference rather
- // than a shallow copy.
- // TODO @davidmo 10/12/2020: remove this in next major version bump and make 'loaders' internal.
- internal List<XRLoader> currentLoaders
- {
- get { return m_Loaders; }
- set { m_Loaders = value; }
- }
-
- // To modify the set of registered loaders use `registeredLoaders` as it will return a reference to the
- // hashset of loaders.
- internal HashSet<XRLoader> registeredLoaders
- {
- get { return m_RegisteredLoaders; }
- }
- }
- }

事情变的有趣起来,我们知道了这样的原理之后,那鱼蛋我就想着尝试下,在Runtime里动态切换行吧,SteamVR场景切换到OpenVR Loader,而XR场景切换到OpenXR,代码如下。
- using System.Collections.Generic;
- using Unity.XR.OpenVR;
- using UnityEngine;
- using UnityEngine.XR.Management;
- using UnityEngine.XR.OpenXR;
-
- namespace EgoGame
- {
- /// <summary>
- /// 该类有问题,废弃了
- /// </summary>
- public class AutoXRLoader:MonoBehaviour
- {
- public List<XRLoader> xrLoaders;
- public List<XRLoader> vrLoaders;
- public bool isXR;
-
- private void Awake()
- {
- SetLoader(isXR);
- }
-
- private void OnDestroy()
- {
- SetLoader(!isXR);
- }
-
- void SetLoader(bool xr)
- {
- //不这样,会频繁的退出loader,VR会没画面
- if (xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenXRLoader)
- {
- return;
- }
- if (!xr && XRGeneralSettings.Instance.Manager.activeLoader is OpenVRLoader)
- {
- return;
- }
- var loaders = xr ? xrLoaders : vrLoaders;
-
- Debug.Log("切换Loader:" + xr+"=="+XRGeneralSettings.Instance.Manager.activeLoader);
- XRGeneralSettings.Instance.Manager.DeinitializeLoader();
- XRGeneralSettings.Instance.Manager.TrySetLoaders(loaders);
- XRGeneralSettings.Instance.Manager.InitializeLoaderSync();
- XRGeneralSettings.Instance.Manager.StartSubsystems();
- }
- }
- }

果然奏效了,XR场景能在头盔里识别并运行了,手柄也能控制。但是,切到SteamVR场景就出现了问题,Steam VR SDK报错了,报错提示有另一个应用在使用SteamVR。
最后的结果就是,没法实现动态切换XR或VR,如果看到此处的人,有办法请告诉我,我尝试了两天用了各种办法,都没法做到。
最后推荐大家开发VR应用不要直接用SteamVR SDK或XR SDK或Oculus SDK开发,而是用那些集成的插件,如VR Interaction Framework、VRTK等,这样在多个VR设备也能快速部署。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。