using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace EPOOutline
{
///
/// The component that is responsible for rendering the ouline and holding its parameters.
///
[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class Outliner : MonoBehaviour
{
#if UNITY_EDITOR
private static GameObject lastSelectedOutliner;
private static List outliners = new List();
#endif
private static List temporaryOutlinables = new List();
private OutlineParameters parameters;
private OutlineParameters Parameters => parameters ??= new OutlineParameters(new BasicCommandBufferWrapper(new CommandBuffer()));
#if UNITY_EDITOR
private OutlineParameters editorPreviewParameters;
private OutlineParameters EditorPreviewParameters => editorPreviewParameters ??= new OutlineParameters(new BasicCommandBufferWrapper(new CommandBuffer()));
#endif
private Camera targetCamera;
[SerializeField]
private RenderStage stage = RenderStage.AfterTransparents;
[SerializeField]
private OutlineRenderingStrategy renderingStrategy = OutlineRenderingStrategy.Default;
[SerializeField]
private RenderingMode renderingMode;
[SerializeField]
private long outlineLayerMask = -1;
[SerializeField]
private BufferSizeMode primaryBufferSizeMode;
[SerializeField]
[Range(0.15f, 1.0f)]
private float primaryRendererScale = 0.75f;
[SerializeField]
private int primarySizeReference = 800;
[SerializeField]
[Range(0.0f, 2.0f)]
private float blurShift = 1.0f;
[SerializeField]
[Range(0.0f, 2.0f)]
private float dilateShift = 1.0f;
[SerializeField]
private int dilateIterations = 1;
[SerializeField]
private DilateQuality dilateQuality;
[SerializeField]
private int blurIterations = 1;
[SerializeField]
private BlurType blurType = BlurType.Box;
private RTHandle target;
private RTHandle primaryBuffer;
private RTHandle targetBuffer;
private CameraEvent Event => stage == RenderStage.BeforeTransparents ? CameraEvent.AfterForwardOpaque : CameraEvent.BeforeImageEffects;
///
/// Used to calculate the buffer size if or is set to .
///
///
///
public int PrimarySizeReference
{
get => primarySizeReference;
set => primarySizeReference = value < 10 ? 50 : value;
}
///
/// that will be used for this outliner.
///
public BufferSizeMode PrimaryBufferSizeMode
{
get => primaryBufferSizeMode;
set => primaryBufferSizeMode = value;
}
///
/// that is going to be used by this outliner.
///
public OutlineRenderingStrategy RenderingStrategy
{
get => renderingStrategy;
set => renderingStrategy = value;
}
///
/// to use for this outliner.
///
public RenderStage RenderStage
{
get => stage;
set => stage = value;
}
///
/// that is going to be used for this outliner.
///
public DilateQuality DilateQuality
{
get => dilateQuality;
set => dilateQuality = value;
}
///
/// that is going to be used for this outliner. For HDRP it's always .
///
public RenderingMode RenderingMode
{
get => renderingMode;
set => renderingMode = value;
}
///
/// Blur shift amount that is going to be applied to this outliner. The more, the higher the shift will be.
///
public float BlurShift
{
get => blurShift;
set => blurShift = Mathf.Clamp(value, 0, 2.0f);
}
///
/// Dilate shift amount that is going to be applied to this outliner. The more, the higher the dilate will be.
///
public float DilateShift
{
get => dilateShift;
set => dilateShift = Mathf.Clamp(value, 0, 2.0f);
}
///
/// The layer mask that will be used to filter the before rendering.
///
///
public long OutlineLayerMask
{
get => outlineLayerMask;
set => outlineLayerMask = value;
}
///
/// The value to scale the primary buffer size if is set to .
///
public float PrimaryRendererScale
{
get => primaryRendererScale;
set => primaryRendererScale = Mathf.Clamp(value, 0.1f, 1.0f);
}
///
/// The count of blur iterations to apply.
///
public int BlurIterations
{
get => blurIterations;
set => blurIterations = value > 0 ? value : 0;
}
///
/// to use for this outliner.
///
public BlurType BlurType
{
get => blurType;
set => blurType = value;
}
///
/// The count of dilate iterations to apply.
///
public int DilateIterations
{
get => dilateIterations;
set => dilateIterations = value > 0 ? value : 0;
}
private void OnValidate()
{
if (blurIterations < 0)
blurIterations = 0;
if (dilateIterations < 0)
dilateIterations = 0;
if (primarySizeReference < 10)
primarySizeReference = 10;
else if (primarySizeReference > 4096)
primarySizeReference = 4096;
primaryRendererScale = Mathf.Clamp(primaryRendererScale, 0.1f, 1.0f);
if (blurType < BlurType.Box)
blurType = BlurType.Box;
if (blurType > BlurType.Gaussian13x13)
blurType = BlurType.Gaussian13x13;
}
private void OnEnable()
{
if (targetCamera == null)
targetCamera = GetComponent();
targetCamera.forceIntoRenderTexture = targetCamera.stereoTargetEye == StereoTargetEyeMask.None || !UnityEngine.XR.XRSettings.enabled;
#if UNITY_EDITOR
outliners.Add(this);
#endif
}
private void OnDestroy()
{
#if UNITY_EDITOR
EditorPreviewParameters.Dispose();
#endif
Parameters.Dispose();
}
private void OnDisable()
{
if (targetCamera != null)
UpdateBuffer(targetCamera, Parameters.Buffer, true);
#if UNITY_EDITOR
RemoveFromAllSceneViews();
outliners.Remove(this);
#endif
#if UNITY_EDITOR
if (RenderPipelineManager.currentPipeline != null)
return;
if (!(EditorPreviewParameters.Buffer is IUnderlyingBufferProvider bufferProvider))
return;
var underlyingBuffer = bufferProvider.UnderlyingBuffer;
foreach (var view in UnityEditor.SceneView.sceneViews)
{
var viewToUpdate = (UnityEditor.SceneView)view;
viewToUpdate.camera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, underlyingBuffer);
viewToUpdate.camera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, underlyingBuffer);
}
#endif
}
private void UpdateBuffer(Camera cameraToUpdate, CommandBufferWrapper buffer, bool removeOnly)
{
if (RenderPipelineManager.currentPipeline != null)
return;
if (!(buffer is IUnderlyingBufferProvider bufferProvider))
return;
var underlyingBuffer = bufferProvider.UnderlyingBuffer;
if (underlyingBuffer == null)
return;
cameraToUpdate.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, underlyingBuffer);
cameraToUpdate.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, underlyingBuffer);
if (removeOnly)
return;
cameraToUpdate.AddCommandBuffer(Event, underlyingBuffer);
}
private void OnPreRender()
{
if (PipelineFetcher.CurrentAsset != null)
return;
Parameters.OutlinablesToRender.Clear();
SetupOutline(targetCamera, Parameters, false);
}
private void SetupOutline(Camera cameraToUse, OutlineParameters parametersToUse, bool isEditor)
{
UpdateBuffer(cameraToUse, parametersToUse.Buffer, false);
PrepareParameters(parametersToUse, cameraToUse, isEditor);
parametersToUse.Buffer.Clear();
if (renderingStrategy == OutlineRenderingStrategy.Default)
{
OutlineEffect.SetupOutline(parametersToUse);
parametersToUse.BlitMesh = null;
parametersToUse.MeshPool.ReleaseAllMeshes();
}
else
{
temporaryOutlinables.Clear();
temporaryOutlinables.AddRange(parametersToUse.OutlinablesToRender);
parametersToUse.OutlinablesToRender.Clear();
parametersToUse.OutlinablesToRender.Add(null);
foreach (var outlinable in temporaryOutlinables)
{
parametersToUse.OutlinablesToRender[0] = outlinable;
OutlineEffect.SetupOutline(parametersToUse);
parametersToUse.BlitMesh = null;
}
parametersToUse.MeshPool.ReleaseAllMeshes();
}
}
#if UNITY_EDITOR
private void RemoveFromAllSceneViews()
{
if (RenderPipelineManager.currentPipeline != null)
return;
foreach (var view in UnityEditor.SceneView.sceneViews)
{
var viewToUpdate = (UnityEditor.SceneView)view;
var eventTransferer = viewToUpdate.camera.GetComponent();
if (eventTransferer != null)
eventTransferer.OnPreRenderEvent -= UpdateEditorCamera;
if (!(EditorPreviewParameters.Buffer is IUnderlyingBufferProvider bufferProvider))
return;
var underlyingBuffer = bufferProvider.UnderlyingBuffer;
viewToUpdate.camera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, underlyingBuffer);
viewToUpdate.camera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, underlyingBuffer);
}
}
private void LateUpdate()
{
if (lastSelectedOutliner == null && outliners.Count > 0)
lastSelectedOutliner = outliners[0].gameObject;
var isSelected = Array.Find(UnityEditor.Selection.gameObjects, x => x == gameObject) ?? lastSelectedOutliner != null;
if (isSelected)
lastSelectedOutliner = gameObject;
foreach (var view in UnityEditor.SceneView.sceneViews)
{
var viewToUpdate = (UnityEditor.SceneView)view;
var eventTransferer = viewToUpdate.camera.GetComponent();
if (eventTransferer != null)
eventTransferer.OnPreRenderEvent -= UpdateEditorCamera;
UpdateBuffer(viewToUpdate.camera, EditorPreviewParameters.Buffer, true);
}
if (!isSelected)
return;
foreach (var view in UnityEditor.SceneView.sceneViews)
{
var viewToUpdate = (UnityEditor.SceneView)view;
if (!viewToUpdate.sceneViewState.showImageEffects)
continue;
var eventTransferer = viewToUpdate.camera.GetComponent();
if (eventTransferer == null)
eventTransferer = viewToUpdate.camera.gameObject.AddComponent();
eventTransferer.OnPreRenderEvent += UpdateEditorCamera;
}
}
private void UpdateEditorCamera(Camera cameraToUpdate)
{
SetupOutline(cameraToUpdate, EditorPreviewParameters, true);
}
#endif
public StereoTargetEyeMask GetTargetEyeMask(Camera cameraTarget)
{
return XRUtility.IsXRActive ? cameraTarget.stereoTargetEye : StereoTargetEyeMask.None;
}
public void UpdateSharedParameters(OutlineParameters parametersToUpdate, Camera cameraToUpdate, bool editorCamera, bool forceNative, bool forceHDR)
{
parametersToUpdate.DilateQuality = DilateQuality;
parametersToUpdate.Camera = cameraToUpdate;
parametersToUpdate.IsEditorCamera = editorCamera;
parametersToUpdate.PrimaryBufferScale = forceNative ? 1.0f : primaryRendererScale;
if (forceNative)
parametersToUpdate.PrimaryBufferSizeMode = BufferSizeMode.Native;
else
{
parametersToUpdate.PrimaryBufferSizeMode = primaryBufferSizeMode;
parametersToUpdate.PrimaryBufferSizeReference = primarySizeReference;
}
parametersToUpdate.BlurIterations = blurIterations;
parametersToUpdate.BlurType = blurType;
parametersToUpdate.DilateIterations = dilateIterations;
parametersToUpdate.BlurShift = blurShift;
parametersToUpdate.DilateShift = dilateShift;
parametersToUpdate.UseHDR = forceHDR || cameraToUpdate.allowHDR && (RenderingMode == RenderingMode.HDR);
parametersToUpdate.EyeMask = GetTargetEyeMask(cameraToUpdate);
parametersToUpdate.OutlineLayerMask = outlineLayerMask;
parametersToUpdate.Prepare();
parametersToUpdate.TextureHandleMap.Clear();
foreach (var outlinable in parametersToUpdate.OutlinablesToRender)
{
for (var index = 0; index < outlinable.OutlineTargets.Count; index++)
{
var outlineTarget = outlinable.OutlineTargets[index];
if (outlineTarget.IsValidForCutout)
{
var cutoutTexture = outlineTarget.CutoutTexture;
var rtHandle = parametersToUpdate.RTHandlePool.Allocate(cutoutTexture);
parametersToUpdate.TextureHandleMap[cutoutTexture] = rtHandle;
}
if (outlineTarget.Renderer is SpriteRenderer spriteRenderer)
{
var texture = spriteRenderer.sprite.texture;
var rtHandle = parametersToUpdate.RTHandlePool.Allocate(texture);
parametersToUpdate.TextureHandleMap[texture] = rtHandle;
}
}
}
}
public void ReplaceHandles(OutlineParameters parametersToUpdate)
{
Replace(ref parametersToUpdate.Handles.Target, parametersToUpdate.TargetWidth, parametersToUpdate.TargetHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Target"));
Replace(ref parametersToUpdate.Handles.InfoTarget, parametersToUpdate.TargetWidth, parametersToUpdate.TargetHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Info target"));
var (scaledWidth, scaledHeight) = parametersToUpdate.ScaledSize;
Replace(ref parametersToUpdate.Handles.PrimaryTarget, scaledWidth, scaledHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Primary target"));
Replace(ref parametersToUpdate.Handles.SecondaryTarget, scaledWidth, scaledHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Secondary target"));
Replace(ref parametersToUpdate.Handles.PrimaryInfoBufferTarget, scaledWidth, scaledHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Primary info target"));
Replace(ref parametersToUpdate.Handles.SecondaryInfoBufferTarget, scaledWidth, scaledHeight, parametersToUpdate,
(width, height, outlineParameters) => RenderTargetUtility.GetRT(outlineParameters, width, height, "Secondary info target"));
}
private static void Replace(ref RTHandle handle, int width, int height, OutlineParameters parameters, Func newHandle)
{
if (handle != null)
{
if (width == handle.rtHandleProperties.currentRenderTargetSize.x &&
height == handle.rtHandleProperties.currentRenderTargetSize.y &&
handle.rt.descriptor.msaaSamples == parameters.Antialiasing)
return;
handle.Release();
}
handle = newHandle(width, height, parameters);
handle.SetCustomHandleProperties(new RTHandleProperties() { currentRenderTargetSize = new Vector2Int(width, height) });
}
private void PrepareParameters(OutlineParameters parametersToPrepare, Camera cameraToUse, bool editorCamera)
{
parametersToPrepare.RTHandlePool.ReleaseAll();
parametersToPrepare.DepthTarget = parametersToPrepare.RTHandlePool.Allocate(RenderTargetUtility.ComposeTarget(parametersToPrepare, BuiltinRenderTextureType.CameraTarget));
parametersToPrepare.Target = parametersToPrepare.RTHandlePool.Allocate(RenderTargetUtility.ComposeTarget(parametersToPrepare, BuiltinRenderTextureType.CameraTarget));
var targetTexture = cameraToUse.targetTexture == null ? cameraToUse.activeTexture : cameraToUse.targetTexture;
if (XRUtility.IsUsingVR(parametersToPrepare))
{
var descriptor = XRUtility.VRRenderTextureDescriptor;
parametersToPrepare.TargetWidth = descriptor.width;
parametersToPrepare.TargetHeight = descriptor.height;
}
else
{
parametersToPrepare.TargetWidth = targetTexture != null ? targetTexture.width : cameraToUse.scaledPixelWidth;
parametersToPrepare.TargetHeight = targetTexture != null ? targetTexture.height : cameraToUse.scaledPixelHeight;
}
parametersToPrepare.Viewport =
new Rect(0, 0, parametersToPrepare.TargetWidth, parametersToPrepare.TargetHeight);
parametersToPrepare.Antialiasing = editorCamera ? (targetTexture == null ? 1 : targetTexture.antiAliasing) : CameraUtility.GetMSAA(targetCamera);
parametersToPrepare.Camera = cameraToUse;
var scaledSize = parametersToPrepare.ScaledSize;
parametersToPrepare.ScaledBufferWidth = scaledSize.ScaledWidth;
parametersToPrepare.ScaledBufferHeight = scaledSize.ScaledHeight;
Outlinable.GetAllActiveOutlinables(parametersToPrepare.OutlinablesToRender);
RendererFilteringUtility.Filter(parametersToPrepare.Camera, parametersToPrepare);
UpdateSharedParameters(parametersToPrepare, cameraToUse, editorCamera, false, false);
ReplaceHandles(parametersToPrepare);
}
}
}