Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Rendering/LOD/UxrLODGroup.cs
2024-08-06 21:58:35 +02:00

222 lines
7.4 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLODGroup.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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
{
/// <summary>
/// Fixes LOD levels in VR, which by default do not work correctly. See
/// https://forum.unity.com/threads/lodgroup-in-vr.455394/.<br />
/// 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.<br />
/// Using LOD switching on teleports only can be disabled using the "Only Fix LOD Bias" parameter.
/// </summary>
[RequireComponent(typeof(LODGroup))]
public class UxrLODGroup : UxrComponent<UxrLODGroup>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _onlyFixLodBias;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the Unity LODGroup component.
/// </summary>
public LODGroup UnityLODGroup => GetCachedComponent<LODGroup>();
#endregion
#region Unity
/// <summary>
/// Subscribes to the global event called whenever any <see cref="UxrLocomotion" /> component is enabled.
/// </summary>
protected override void Awake()
{
base.Awake();
if (!_onlyFixLodBias)
{
UxrAvatar.LocalAvatarChanged += UxrAvatar_LocalAvatarChanged;
}
}
/// <summary>
/// Unsubscribes from the global event called whenever any <see cref="UxrLocomotion" /> component is enabled.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (!_onlyFixLodBias)
{
UxrAvatar.LocalAvatarChanged -= UxrAvatar_LocalAvatarChanged;
}
}
/// <summary>
/// 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.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (!_onlyFixLodBias)
{
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
UpdateMode(UxrAvatar.LocalAvatar);
}
StartCoroutine(FixLodBiasCoroutine());
}
/// <summary>
/// Unsubscribes from the event called whenever an avatar was moved.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (!_onlyFixLodBias)
{
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
UnityLODGroup.ForceLOD(-1);
}
}
#endregion
#region Coroutines
/// <summary>
/// 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/
/// </summary>
/// <returns>Coroutine enumerator</returns>
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
/// <summary>
/// Called whenever the local avatar changed. We use it to store whether the current local avatar
/// uses smooth locomotion.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void UxrAvatar_LocalAvatarChanged(object sender, UxrAvatarEventArgs e)
{
UpdateMode(e.Avatar);
}
/// <summary>
/// Called whenever an avatar moved. In non-smooth locomotion mode it will be used to switch LOD levels manually.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
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
/// <summary>
/// Manually enables the LOD levels for a given camera.
/// </summary>
/// <param name="cam"></param>
private void EnableLevelRenderers(Camera cam)
{
UnityLODGroup.ForceLOD(UnityLODGroup.GetVisibleLevel(cam));
}
/// <summary>
/// Updates the continuous/discrete operation mode of the component.
/// </summary>
/// <param name="avatar">Avatar to update the current mode for</param>
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
}
}