// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections; using UltimateXR.Avatar; using UltimateXR.Core; using UltimateXR.Core.Components; using UltimateXR.Core.Settings; using UltimateXR.Extensions.Unity.Render; using UltimateXR.Locomotion; using UnityEngine; namespace UltimateXR.Rendering.LOD { /// /// Fixes LOD levels in VR, which by default do not work correctly. See /// https://forum.unity.com/threads/lodgroup-in-vr.455394/.
/// In addition, when using non-smooth locomotion, it can switch LOD levels only when the avatar moved. /// This avoids LOD switching caused by head movement, where the camera is. When using a smooth locomotion /// system it will use regular LOD switching.
/// Using LOD switching on teleports only can be disabled using the "Only Fix LOD Bias" parameter. ///
[RequireComponent(typeof(LODGroup))] public class UxrLODGroup : UxrComponent { #region Inspector Properties/Serialized Fields [SerializeField] private bool _onlyFixLodBias; #endregion #region Public Types & Data /// /// Gets the Unity LODGroup component. /// public LODGroup UnityLODGroup => GetCachedComponent(); #endregion #region Unity /// /// Subscribes to the global event called whenever any component is enabled. /// protected override void Awake() { base.Awake(); if (!_onlyFixLodBias) { UxrAvatar.LocalAvatarChanged += UxrAvatar_LocalAvatarChanged; } } /// /// Unsubscribes from the global event called whenever any component is enabled. /// protected override void OnDestroy() { base.OnDestroy(); if (!_onlyFixLodBias) { UxrAvatar.LocalAvatarChanged -= UxrAvatar_LocalAvatarChanged; } } /// /// Subscribes to the event called whenever an avatar was moved. /// Also starts the LOD Bias fix coroutine and sets up the correct LOD level if there is a local avatar. /// protected override void OnEnable() { base.OnEnable(); if (!_onlyFixLodBias) { UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved; UpdateMode(UxrAvatar.LocalAvatar); } StartCoroutine(FixLodBiasCoroutine()); } /// /// Unsubscribes from the event called whenever an avatar was moved. /// protected override void OnDisable() { base.OnDisable(); if (!_onlyFixLodBias) { UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved; UnityLODGroup.ForceLOD(-1); } } #endregion #region Coroutines /// /// Fixes the LOD bias so that the LOD switching in VR behaves like in the editor. /// From: https://forum.unity.com/threads/lodgroup-in-vr.455394/ /// /// Coroutine enumerator private IEnumerator FixLodBiasCoroutine() { // Wait until the camera gets the correct VR FOV yield return null; yield return null; yield return null; while (UxrAvatar.LocalAvatarCamera == null) { if (UxrAvatar.LocalAvatar != null && UxrAvatar.LocalStandardAvatarController == null) { // Replay or other. yield break; } yield return null; } // Fix? if (!s_lodGroupFixed) { float oldLodBias = QualitySettings.lodBias; float editorCameraRadians = Mathf.PI / 3.0f; QualitySettings.lodBias *= Mathf.Tan(UxrAvatar.LocalAvatarCamera.fieldOfView * Mathf.Deg2Rad / 2) / Mathf.Tan(editorCameraRadians / 2); s_lodGroupFixed = true; if (UxrGlobalSettings.Instance.LogLevelRendering >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.RenderingModule}: Fixing LOD Bias for VR cameras. Old value = {oldLodBias}, new value = {QualitySettings.lodBias}."); } } } #endregion #region Event Handling Methods /// /// Called whenever the local avatar changed. We use it to store whether the current local avatar /// uses smooth locomotion. /// /// Event sender /// Event parameters private void UxrAvatar_LocalAvatarChanged(object sender, UxrAvatarEventArgs e) { UpdateMode(e.Avatar); } /// /// Called whenever an avatar moved. In non-smooth locomotion mode it will be used to switch LOD levels manually. /// /// Event sender /// Event parameters private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e) { UxrAvatar avatar = sender as UxrAvatar; if (avatar == UxrAvatar.LocalAvatar && !_isSmoothLocomotionEnabled) { EnableLevelRenderers(avatar.CameraComponent); } } #endregion #region Private Methods /// /// Manually enables the LOD levels for a given camera. /// /// private void EnableLevelRenderers(Camera cam) { UnityLODGroup.ForceLOD(UnityLODGroup.GetVisibleLevel(cam)); } /// /// Updates the continuous/discrete operation mode of the component. /// /// Avatar to update the current mode for private void UpdateMode(UxrAvatar avatar) { _isSmoothLocomotionEnabled = avatar != null && avatar.AvatarController != null && avatar.AvatarController.UsesSmoothLocomotion; bool autoLod = _isSmoothLocomotionEnabled || avatar == null || avatar.CameraComponent == null; if (!autoLod) { if (avatar != null && avatar.CameraComponent != null) { EnableLevelRenderers(avatar.CameraComponent); } } else { UnityLODGroup.EnableAllLevelRenderers(); UnityLODGroup.ForceLOD(-1); } } #endregion #region Private Types & Data private static bool s_lodGroupFixed; private bool _isSmoothLocomotionEnabled = true; #endregion } }