531 lines
21 KiB
C#
531 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace EPOOutline
|
|
{
|
|
/// <summary>
|
|
/// The component that is responsible for rendering the ouline and holding its parameters.
|
|
/// </summary>
|
|
[ExecuteAlways]
|
|
[RequireComponent(typeof(Camera))]
|
|
public class Outliner : MonoBehaviour
|
|
{
|
|
#if UNITY_EDITOR
|
|
private static GameObject lastSelectedOutliner;
|
|
|
|
private static List<Outliner> outliners = new List<Outliner>();
|
|
#endif
|
|
|
|
private static List<Outlinable> temporaryOutlinables = new List<Outlinable>();
|
|
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Used to calculate the buffer size if <see cref="EPOOutline.BufferSizeMode.WidthControlsHeight"/> or <see cref="EPOOutline.BufferSizeMode.HeightControlsWidth"/> is set to <see cref="EPOOutline.Outliner.PrimaryBufferSizeMode"/>.
|
|
/// <seealso cref="EPOOutline.BufferSizeMode.WidthControlsHeight"/>
|
|
/// <seealso cref="EPOOutline.BufferSizeMode.HeightControlsWidth"/>
|
|
/// </summary>
|
|
public int PrimarySizeReference
|
|
{
|
|
get => primarySizeReference;
|
|
set => primarySizeReference = value < 10 ? 50 : value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.BufferSizeMode"/> that will be used for this outliner.
|
|
/// </summary>
|
|
public BufferSizeMode PrimaryBufferSizeMode
|
|
{
|
|
get => primaryBufferSizeMode;
|
|
set => primaryBufferSizeMode = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.OutlineRenderingStrategy"/> that is going to be used by this outliner.
|
|
/// </summary>
|
|
public OutlineRenderingStrategy RenderingStrategy
|
|
{
|
|
get => renderingStrategy;
|
|
set => renderingStrategy = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.RenderStage"/> to use for this outliner.
|
|
/// </summary>
|
|
public RenderStage RenderStage
|
|
{
|
|
get => stage;
|
|
set => stage = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.DilateQuality"/> that is going to be used for this outliner.
|
|
/// </summary>
|
|
public DilateQuality DilateQuality
|
|
{
|
|
get => dilateQuality;
|
|
set => dilateQuality = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.RenderingMode"/> that is going to be used for this outliner. For HDRP it's always <see cref="EPOOutline.RenderingMode.HDR"/>.
|
|
/// </summary>
|
|
public RenderingMode RenderingMode
|
|
{
|
|
get => renderingMode;
|
|
set => renderingMode = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blur shift amount that is going to be applied to this outliner. The more, the higher the shift will be.
|
|
/// </summary>
|
|
public float BlurShift
|
|
{
|
|
get => blurShift;
|
|
set => blurShift = Mathf.Clamp(value, 0, 2.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dilate shift amount that is going to be applied to this outliner. The more, the higher the dilate will be.
|
|
/// </summary>
|
|
public float DilateShift
|
|
{
|
|
get => dilateShift;
|
|
set => dilateShift = Mathf.Clamp(value, 0, 2.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The layer mask that will be used to filter the <see cref="EPOOutline.Outlinable"/> before rendering.
|
|
/// <seealso cref="EPOOutline.Outlinable.OutlineLayer"/>
|
|
/// </summary>
|
|
public long OutlineLayerMask
|
|
{
|
|
get => outlineLayerMask;
|
|
set => outlineLayerMask = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The value to scale the primary buffer size if <see cref="EPOOutline.Outliner.PrimaryBufferSizeMode"/> is set to <see cref="EPOOutline.BufferSizeMode.Scaled"/>.
|
|
/// </summary>
|
|
public float PrimaryRendererScale
|
|
{
|
|
get => primaryRendererScale;
|
|
set => primaryRendererScale = Mathf.Clamp(value, 0.1f, 1.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of blur iterations to apply.
|
|
/// </summary>
|
|
public int BlurIterations
|
|
{
|
|
get => blurIterations;
|
|
set => blurIterations = value > 0 ? value : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="EPOOutline.BlurType"/> to use for this outliner.
|
|
/// </summary>
|
|
public BlurType BlurType
|
|
{
|
|
get => blurType;
|
|
set => blurType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of dilate iterations to apply.
|
|
/// </summary>
|
|
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<Camera>();
|
|
|
|
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<OnPreRenderEventTransferer>();
|
|
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<OnPreRenderEventTransferer>();
|
|
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<OnPreRenderEventTransferer>();
|
|
if (eventTransferer == null)
|
|
eventTransferer = viewToUpdate.camera.gameObject.AddComponent<OnPreRenderEventTransferer>();
|
|
|
|
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<int, int, OutlineParameters, RTHandle> 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);
|
|
}
|
|
}
|
|
} |