DongYingLiangGuanYiGong/Assets/Plugins/HighlightingSystem/Scripts/Internal/HighlighterCore.cs

548 lines
14 KiB
C#

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace HighlightingSystem
{
public enum HighlighterMode : int
{
Disabled = -1,
Default = 0,
Overlay = 1,
Occluder = 2,
}
[DisallowMultipleComponent]
public class HighlighterCore : MonoBehaviour
{
[UnityEngine.Internal.ExcludeFromDocs]
private class RendererData
{
public Renderer renderer;
public List<int> submeshIndices = new List<int>();
}
// Constants (don't touch this!)
#region Constants
//
[UnityEngine.Internal.ExcludeFromDocs]
public const string keywordOverlay = "HIGHLIGHTING_OVERLAY";
// Occlusion color
private readonly Color occluderColor = new Color(0f, 0f, 0f, 0f);
// Highlighting modes rendered in that order
static private readonly HighlighterMode[] renderingOrder = new HighlighterMode[]
{
HighlighterMode.Default,
HighlighterMode.Overlay,
HighlighterMode.Occluder
};
// Pool chunk size for sRendererDataPool expansion
private const int poolChunkSize = 4;
#endregion
#region Static Fields
// Static collections to prevent unnecessary memory allocations for grabbing and filtering renderer components
static private readonly List<Renderer> sRenderers = new List<Renderer>(4);
static private readonly Stack<RendererData> sRendererDataPool = new Stack<RendererData>();
static private readonly List<RendererData> sRendererData = new List<RendererData>(4);
static private readonly List<int> sSubmeshIndices = new List<int>(4);
static private readonly List<HighlighterCore> sHighlighters = new List<HighlighterCore>(); // Collection of all enabled highlighters
static private ReadOnlyCollection<HighlighterCore> sHighlightersReadonly;
#endregion
#region Public Accessors
static public ReadOnlyCollection<HighlighterCore> highlighters
{
get
{
if (sHighlightersReadonly == null)
{
sHighlightersReadonly = sHighlighters.AsReadOnly();
}
return sHighlightersReadonly;
}
}
#endregion
#region Public Fields
/// <summary>
/// Defines how this highlighter instance will be rendered.
/// </summary>
public HighlighterMode mode = HighlighterMode.Default;
/// <summary>
/// Force-render this Highlighter. No culling is performed in this case (neither frustum nor occlusion culling) and renderers from all LOD levels will be always rendered.
/// Please be considerate in enabling this mode, or you may experience performance degradation.
/// </summary>
public bool forceRender;
/// <summary>
/// Highlighting color.
/// </summary>
public Color color = Color.white;
#endregion
#region Private Fields
// Cached transform component reference
private Transform tr;
// Cached Renderers
private List<HighlighterRenderer> highlightableRenderers = new List<HighlighterRenderer>();
// Renderers dirty flag
private bool isDirty = true;
// Cached highlighting mode
private bool cachedOverlay = false;
// Cached highlighting color
private Color cachedColor = Color.clear;
// Opaque shader cached reference
static private Shader _opaqueShader;
[UnityEngine.Internal.ExcludeFromDocs]
static public Shader opaqueShader
{
get
{
if (_opaqueShader == null)
{
_opaqueShader = Shader.Find("Hidden/Highlighted/Opaque");
}
return _opaqueShader;
}
}
// Transparent shader cached reference
static private Shader _transparentShader;
[UnityEngine.Internal.ExcludeFromDocs]
static public Shader transparentShader
{
get
{
if (_transparentShader == null)
{
_transparentShader = Shader.Find("Hidden/Highlighted/Transparent");
}
return _transparentShader;
}
}
// Shared (for this component) replacement material for opaque geometry highlighting
private Material _opaqueMaterial;
private Material opaqueMaterial
{
get
{
if (_opaqueMaterial == null)
{
_opaqueMaterial = new Material(opaqueShader);
// Make sure that shader will have proper default values
_opaqueMaterial.SetKeyword(keywordOverlay, cachedOverlay);
_opaqueMaterial.SetColor(ShaderPropertyID._HighlightingColor, cachedColor);
}
return _opaqueMaterial;
}
}
#endregion
#region Renderers Filtration
public delegate bool RendererFilter(Renderer renderer, List<int> submeshIndices);
static private RendererFilter _globalRendererFilter = null;
static public RendererFilter globalRendererFilter
{
get { return _globalRendererFilter; }
set
{
if (_globalRendererFilter != value)
{
_globalRendererFilter = value;
for (int i = sHighlighters.Count - 1; i >= 0; i--)
{
var highlighter = sHighlighters[i];
// Cleanup null highlighters for extra safety
if (highlighter == null)
{
sHighlighters.RemoveAt(i);
continue;
}
// Reinitialize all Highlighters without explicitly assigned rendererFilters
if (highlighter.rendererFilter == null)
{
highlighter.SetDirty();
}
}
}
}
}
private RendererFilter _rendererFilter = null;
public RendererFilter rendererFilter
{
get { return _rendererFilter; }
set
{
if (_rendererFilter != value)
{
_rendererFilter = value;
SetDirty();
}
}
}
protected virtual RendererFilter rendererFilterToUse
{
get
{
if (_rendererFilter != null)
{
return _rendererFilter;
}
else if (_globalRendererFilter != null)
{
return _globalRendererFilter;
}
else
{
return DefaultRendererFilter;
}
}
}
//
static public bool DefaultRendererFilter(Renderer renderer, List<int> submeshIndices)
{
// Do not highlight this renderer if it has HighlighterBlocker in parent
if (renderer.GetComponentInParent<HighlighterBlocker>() != null) { return false; }
bool pass = false;
if (renderer is MeshRenderer) { pass = true; }
else if (renderer is SkinnedMeshRenderer) { pass = true; }
else if (renderer is SpriteRenderer) { pass = true; }
else if (renderer is ParticleSystemRenderer) { pass = true; }
if (pass)
{
// Highlight all submeshes
submeshIndices.Add(-1);
}
return pass;
}
#endregion
#region MonoBehaviour
// Override and use AwakeSafe instead of this method
private void Awake()
{
tr = GetComponent<Transform>();
AwakeSafe();
}
// Override and use OnEnableSafe instead of this method
private void OnEnable()
{
if (!sHighlighters.Contains(this)) { sHighlighters.Add(this); }
OnEnableSafe();
}
// Override and use OnDisableSafe instead of this method
private void OnDisable()
{
sHighlighters.Remove(this);
ClearRenderers();
isDirty = true;
OnDisableSafe();
}
//
private void OnDestroy()
{
// Unity never garbage-collects unreferenced materials, so it is our responsibility to destroy them
if (_opaqueMaterial != null)
{
Destroy(_opaqueMaterial);
}
OnDestroySafe();
}
#endregion
#region MonoBehaviour Overrides
//
protected virtual void AwakeSafe() { }
//
protected virtual void OnEnableSafe() { }
//
protected virtual void OnDisableSafe() { }
//
protected virtual void OnDestroySafe() { }
#endregion
#region Public Methods
/// <summary>
/// Reinitialize renderers.
/// Call this method if your highlighted object has changed it's materials, renderers or child objects.
/// Can be called multiple times per update - renderers reinitialization will occur only once.
/// </summary>
public void SetDirty()
{
isDirty = true;
}
#endregion
#region Protected Methods
//
protected virtual void UpdateHighlighting()
{
// Update highlighting mode and color here.
}
#endregion
#region Private Methods
// Clear cached renderers
private void ClearRenderers()
{
for (int i = highlightableRenderers.Count - 1; i >= 0; i--)
{
HighlighterRenderer renderer = highlightableRenderers[i];
renderer.isAlive = false;
}
highlightableRenderers.Clear();
}
// This method defines how renderers are initialized
private void UpdateRenderers()
{
if (isDirty)
{
isDirty = false;
ClearRenderers();
// Find all renderers which should be highlighted with this HighlighterCore component instance
GrabRenderers(tr);
// Cache found renderers
for (int i = 0, imax = sRendererData.Count; i < imax; i++)
{
RendererData rendererData = sRendererData[i];
GameObject rg = rendererData.renderer.gameObject;
HighlighterRenderer renderer = rg.GetComponent<HighlighterRenderer>();
if (renderer == null) { renderer = rg.AddComponent<HighlighterRenderer>(); }
renderer.isAlive = true;
renderer.Initialize(opaqueMaterial, transparentShader, rendererData.submeshIndices);
renderer.SetOverlay(cachedOverlay);
renderer.SetColor(cachedColor);
highlightableRenderers.Add(renderer);
}
// Release RendererData instances
for (int i = 0; i < sRendererData.Count; i++)
{
ReleaseRendererDataInstance(sRendererData[i]);
}
sRendererData.Clear();
}
}
// Recursively follows hierarchy of objects from t, searches for Renderers and adds them to the list.
// Breaks if another Highlighter component found
private void GrabRenderers(Transform t)
{
GameObject g = t.gameObject;
// Find all Renderers on the current GameObject g, filter them and add to the sRendererData list
g.GetComponents<Renderer>(sRenderers);
for (int i = 0, imax = sRenderers.Count; i < imax; i++)
{
Renderer renderer = sRenderers[i];
if (rendererFilterToUse(renderer, sSubmeshIndices))
{
RendererData rendererData = GetRendererDataInstance();
rendererData.renderer = renderer;
List<int> submeshIndices = rendererData.submeshIndices;
submeshIndices.Clear();
submeshIndices.AddRange(sSubmeshIndices);
sRendererData.Add(rendererData);
}
sSubmeshIndices.Clear();
}
sRenderers.Clear();
// Return if transform t doesn't have any children
int childCount = t.childCount;
if (childCount == 0) { return; }
// Recursively cache renderers on all child GameObjects
for (int i = 0; i < childCount; i++)
{
Transform childTransform = t.GetChild(i);
// Do not cache Renderers of this childTransform in case it has it's own Highlighter component
HighlighterCore h = childTransform.GetComponent<HighlighterCore>();
if (h != null) { continue; }
GrabRenderers(childTransform);
}
}
//
private void FillBufferInternal(CommandBuffer buffer)
{
// Set cachedOverlay if changed
bool overlayToUse = mode == HighlighterMode.Overlay || mode == HighlighterMode.Occluder;
if (cachedOverlay != overlayToUse)
{
cachedOverlay = overlayToUse;
opaqueMaterial.SetKeyword(keywordOverlay, cachedOverlay);
for (int i = 0; i < highlightableRenderers.Count; i++)
{
highlightableRenderers[i].SetOverlay(cachedOverlay);
}
}
// Set cachedColor if changed
Color colorToUse = mode != HighlighterMode.Occluder ? color : occluderColor;
if (cachedColor != colorToUse)
{
cachedColor = colorToUse;
// Apply color
opaqueMaterial.SetColor(ShaderPropertyID._HighlightingColor, cachedColor);
for (int i = 0; i < highlightableRenderers.Count; i++)
{
highlightableRenderers[i].SetColor(cachedColor);
}
}
// Fill CommandBuffer with this highlighter rendering commands
for (int i = highlightableRenderers.Count - 1; i >= 0; i--)
{
// To avoid null-reference exceptions when cached renderer has been removed but SetDirty() wasn't been called
HighlighterRenderer renderer = highlightableRenderers[i];
if (renderer == null)
{
highlightableRenderers.RemoveAt(i);
}
else if (!renderer.IsValid())
{
highlightableRenderers.RemoveAt(i);
renderer.isAlive = false;
}
else
{
// Check if renderer is visible for the currently rendering camera.
// (Last camera called OnWillRenderObject on HighlighterRenderer instance is the same camera for which we're currently filling CommandBuffer)
if (HighlightingBase.GetVisible(renderer) || forceRender)
{
renderer.FillBuffer(buffer);
}
}
}
}
#endregion
#region Static Methods
//
static private void ExpandRendererDataPool(int count)
{
for (int i = 0; i < count; i++)
{
RendererData instance = new RendererData();
sRendererDataPool.Push(instance);
}
}
//
static private RendererData GetRendererDataInstance()
{
RendererData instance;
if (sRendererDataPool.Count == 0)
{
ExpandRendererDataPool(poolChunkSize);
}
instance = sRendererDataPool.Pop();
return instance;
}
//
static private void ReleaseRendererDataInstance(RendererData instance)
{
if (instance == null || sRendererDataPool.Contains(instance)) { return; }
instance.renderer = null;
instance.submeshIndices.Clear();
sRendererDataPool.Push(instance);
}
// Fill CommandBuffer with highlighters rendering commands
[UnityEngine.Internal.ExcludeFromDocs]
static public void FillBuffer(CommandBuffer buffer)
{
// Update all highlighters
for (int i = sHighlighters.Count - 1; i >= 0; i--)
{
var highlighter = sHighlighters[i];
// Cleanup null highlighters for extra safety
if (highlighter == null)
{
sHighlighters.RemoveAt(i);
continue;
}
// Update mode and color
highlighter.UpdateHighlighting();
// Cleanup again in case Highlighter has been destroyed in UpdateHighlighting() call
if (highlighter == null)
{
sHighlighters.RemoveAt(i);
continue;
}
// Update Renderer's if dirty
highlighter.UpdateRenderers();
}
// Fill CommandBuffer with highlighters rendering commands
for (int i = 0; i < renderingOrder.Length; i++)
{
HighlighterMode renderMode = renderingOrder[i];
for (int j = sHighlighters.Count - 1; j >= 0; j--)
{
var highlighter = sHighlighters[j];
if (highlighter.mode == renderMode)
{
highlighter.FillBufferInternal(buffer);
}
}
}
}
#endregion
}
}