using UnityEngine;
using UnityEngine.Serialization;
namespace VLB
{
[AddComponentMenu("")] // hide it from Component search
[DisallowMultipleComponent]
[RequireComponent(typeof(VolumetricLightBeamSD))]
public abstract class DynamicOcclusionAbstractBase : MonoBehaviour
{
public const string ClassName = "DynamicOcclusionAbstractBase";
///
/// How often will the occlusion be processed?
/// Try to update the occlusion as rarely as possible to keep good performance.
///
public DynamicOcclusionUpdateRate updateRate = Consts.DynOcclusion.UpdateRateDefault;
///
/// How many frames we wait between 2 occlusion tests?
/// If you want your beam to be super responsive to the changes of your environment, update it every frame by setting 1.
/// If you want to save on performance, we recommend to wait few frames between each update by setting a higher value.
///
[FormerlySerializedAs("waitFrameCount")]
public int waitXFrames = Consts.DynOcclusion.WaitFramesCountDefault;
///
/// Manually process the occlusion.
/// You have to call this function in order to update the occlusion when using DynamicOcclusionUpdateRate.Never.
///
public void ProcessOcclusionManually() { ProcessOcclusion(ProcessOcclusionSource.User); }
public event System.Action onOcclusionProcessed;
public static bool _INTERNAL_ApplyRandomFrameOffset = true;
protected enum ProcessOcclusionSource
{
RenderLoop,
OnEnable,
EditorUpdate,
User,
}
protected void ProcessOcclusion(ProcessOcclusionSource source)
{
if (!Config.Instance.featureEnabledDynamicOcclusion)
return;
if (m_LastFrameRendered == Time.frameCount && Application.isPlaying && source == ProcessOcclusionSource.OnEnable)
return; // allow to call ProcessOcclusion from OnEnable (when disabling/enabling multiple a beam on the same frame) without generating an error
Debug.Assert(!Application.isPlaying || m_LastFrameRendered != Time.frameCount, "ProcessOcclusion has been called twice on the same frame, which is forbidden");
Debug.Assert(m_Master);
bool occlusionSuccess = OnProcessOcclusion(source);
if(onOcclusionProcessed != null)
onOcclusionProcessed();
if (m_Master)
{
Debug.Assert(m_MaterialModifierCallbackCached != null);
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), occlusionSuccess ? m_MaterialModifierCallbackCached : (MaterialModifier.Callback)(null));
}
if (updateRate.HasFlag(DynamicOcclusionUpdateRate.OnBeamMove))
m_TransformPacked = transform.GetWorldPacked();
bool firstTime = m_LastFrameRendered < 0;
m_LastFrameRendered = Time.frameCount;
if (firstTime && _INTERNAL_ApplyRandomFrameOffset)
{
m_LastFrameRendered += Random.Range(0, waitXFrames); // add a random offset to prevent from updating texture for all beams having the same wait value
}
}
TransformUtils.Packed m_TransformPacked;
int m_LastFrameRendered = int.MinValue;
public int _INTERNAL_LastFrameRendered { get { return m_LastFrameRendered; } } // for unit tests
protected VolumetricLightBeamSD m_Master = null;
protected MaterialModifier.Callback m_MaterialModifierCallbackCached = null;
protected abstract string GetShaderKeyword();
protected abstract MaterialManager.SD.DynamicOcclusion GetDynamicOcclusionMode();
protected abstract bool OnProcessOcclusion(ProcessOcclusionSource source);
protected abstract void OnModifyMaterialCallback(MaterialModifier.Interface owner);
protected abstract void OnEnablePostValidate();
protected virtual void OnValidateProperties()
{
waitXFrames = Mathf.Clamp(waitXFrames, 1, 60);
}
protected virtual void Awake()
{
m_Master = GetComponent();
Debug.Assert(m_Master);
m_Master._INTERNAL_DynamicOcclusionMode = GetDynamicOcclusionMode();
}
protected virtual void OnDestroy()
{
m_Master._INTERNAL_DynamicOcclusionMode = MaterialManager.SD.DynamicOcclusion.Off;
DisableOcclusion();
}
protected virtual void OnEnable()
{
// cache the delegate to prevent from being inlined as '() => OnModifyMaterialCallback' when calling _INTERNAL_SetDynamicOcclusionCallback and from generating GC garbage
m_MaterialModifierCallbackCached = OnModifyMaterialCallback;
OnValidateProperties();
OnEnablePostValidate();
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
m_Master.onWillCameraRenderThisBeam += OnWillCameraRender;
if (!updateRate.HasFlag(DynamicOcclusionUpdateRate.Never))
m_Master.RegisterOnBeamGeometryInitializedCallback(() => ProcessOcclusion(ProcessOcclusionSource.OnEnable));
}
}
protected virtual void OnDisable()
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
m_Master.onWillCameraRenderThisBeam -= OnWillCameraRender;
}
DisableOcclusion();
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
OnValidateProperties();
}
#endif
void OnWillCameraRender(Camera cam)
{
Debug.Assert(Application.isPlaying);
if (cam != null && cam.enabled
&& Time.frameCount != m_LastFrameRendered) // prevent from updating multiple times if there are more than 1 camera
{
bool shouldUpdate = false;
if (!shouldUpdate && updateRate.HasFlag(DynamicOcclusionUpdateRate.OnBeamMove))
{
if (!m_TransformPacked.IsSame(transform))
shouldUpdate = true;
}
if (!shouldUpdate && updateRate.HasFlag(DynamicOcclusionUpdateRate.EveryXFrames))
{
if (Time.frameCount >= m_LastFrameRendered + waitXFrames)
shouldUpdate = true;
}
if (shouldUpdate)
ProcessOcclusion(ProcessOcclusionSource.RenderLoop);
}
}
void DisableOcclusion()
{
m_Master._INTERNAL_SetDynamicOcclusionCallback(GetShaderKeyword(), null);
}
}
}