init
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic outline settings.
|
||||
/// </summary>
|
||||
public interface IOutlineSettings : IEquatable<IOutlineSettings>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets outline color.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineWidth"/>
|
||||
/// <seealso cref="OutlineRenderMode"/>
|
||||
Color OutlineColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets outline width in pixels. Allowed range is [<see cref="OutlineRenderer.MinWidth"/>, <see cref="OutlineRenderer.MaxWidth"/>].
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineColor"/>
|
||||
/// <seealso cref="OutlineRenderMode"/>
|
||||
int OutlineWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets outline intensity value. Allowed range is [<see cref="OutlineRenderer.MinIntensity"/>, <see cref="OutlineRenderer.MaxIntensity"/>].
|
||||
/// This is used for blurred oulines only (i.e. <see cref="OutlineRenderMode"/> has <see cref="OutlineRenderFlags.Blurred"/> flag).
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineRenderMode"/>
|
||||
/// <seealso cref="OutlineColor"/>
|
||||
/// <seealso cref="OutlineWidth"/>
|
||||
float OutlineIntensity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets alpha cutoff value. Allowed range is [0, 1]. This is used only when <see cref="OutlineRenderMode"/> has <see cref="OutlineRenderFlags.EnableAlphaTesting"/> flag.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineRenderMode"/>
|
||||
float OutlineAlphaCutoff { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets outline render mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineWidth"/>
|
||||
/// <seealso cref="OutlineColor"/>
|
||||
/// <seealso cref="OutlineIntensity"/>
|
||||
OutlineRenderFlags OutlineRenderMode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efc18f75d5206f14a80e9306650c858a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,443 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Attach this script to a <see cref="GameObject"/> to add outline effect. It can be configured in edit-time or in runtime via scripts.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineEffect"/>
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class OutlineBehaviour : MonoBehaviour, IOutlineSettings
|
||||
{
|
||||
#region data
|
||||
|
||||
#pragma warning disable 0649
|
||||
|
||||
[SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)]
|
||||
private OutlineResources _outlineResources;
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineSettingsInstance _outlineSettings;
|
||||
[SerializeField, HideInInspector]
|
||||
private int _ignoreLayerMask;
|
||||
[SerializeField, HideInInspector]
|
||||
private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent;
|
||||
[SerializeField, HideInInspector]
|
||||
private Camera _targetCamera;
|
||||
[SerializeField, Tooltip("If set, list of object renderers is updated on each frame. Enable if the object has child renderers which are enabled/disabled frequently.")]
|
||||
private bool _updateRenderers;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
private Dictionary<Camera, CommandBuffer> _cameraMap = new Dictionary<Camera, CommandBuffer>();
|
||||
private List<Camera> _camerasToRemove = new List<Camera>();
|
||||
private OutlineRendererCollection _renderers;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets resources used by the effect implementation.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if setter argument is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="OutlineSettings"/>
|
||||
public OutlineResources OutlineResources
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineResources;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(OutlineResources));
|
||||
}
|
||||
|
||||
_outlineResources = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets outline settings. Set this to non-<see langword="null"/> value to share settings with other components.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineResources"/>
|
||||
public OutlineSettings OutlineSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_outlineSettings == null)
|
||||
{
|
||||
_outlineSettings = new OutlineSettingsInstance();
|
||||
}
|
||||
|
||||
return _outlineSettings.OutlineSettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_outlineSettings == null)
|
||||
{
|
||||
_outlineSettings = new OutlineSettingsInstance();
|
||||
}
|
||||
|
||||
_outlineSettings.OutlineSettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets layer mask to use for ignored <see cref="Renderer"/> components in this game object.
|
||||
/// </summary>
|
||||
public int IgnoreLayerMask
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ignoreLayerMask;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_ignoreLayerMask != value)
|
||||
{
|
||||
_ignoreLayerMask = value;
|
||||
_renderers?.Reset(false, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets <see cref="CameraEvent"/> used to render the outlines.
|
||||
/// </summary>
|
||||
public CameraEvent RenderEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cameraEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_cameraEvent != value)
|
||||
{
|
||||
foreach (var kvp in _cameraMap)
|
||||
{
|
||||
if (kvp.Key)
|
||||
{
|
||||
kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
|
||||
kvp.Key.AddCommandBuffer(value, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
_cameraEvent = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets outline renderers. By default all child <see cref="Renderer"/> components are used for outlining.
|
||||
/// </summary>
|
||||
/// <seealso cref="UpdateRenderers"/>
|
||||
public ICollection<Renderer> OutlineRenderers
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateRenderersIfNeeded();
|
||||
return _renderers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets camera to render outlines to. If not set, outlines are rendered to all active cameras.
|
||||
/// </summary>
|
||||
/// <seealso cref="Cameras"/>
|
||||
public Camera Camera
|
||||
{
|
||||
get
|
||||
{
|
||||
return _targetCamera;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_targetCamera != value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_camerasToRemove.Clear();
|
||||
|
||||
foreach (var kvp in _cameraMap)
|
||||
{
|
||||
if (kvp.Key && kvp.Key != value)
|
||||
{
|
||||
kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
|
||||
kvp.Value.Dispose();
|
||||
|
||||
_camerasToRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var camera in _camerasToRemove)
|
||||
{
|
||||
_cameraMap.Remove(camera);
|
||||
}
|
||||
}
|
||||
|
||||
_targetCamera = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all cameras outline data is rendered to.
|
||||
/// </summary>
|
||||
/// <seealso cref="Camera"/>
|
||||
public ICollection<Camera> Cameras => _cameraMap.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Updates renderer list.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineRenderers"/>
|
||||
public void UpdateRenderers()
|
||||
{
|
||||
_renderers?.Reset(false, _ignoreLayerMask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
OutlineResources.LogSrpNotSupported(this);
|
||||
OutlineResources.LogPpNotSupported(this);
|
||||
|
||||
CreateRenderersIfNeeded();
|
||||
CreateSettingsIfNeeded();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Camera.onPreRender += OnCameraPreRender;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Camera.onPreRender -= OnCameraPreRender;
|
||||
|
||||
foreach (var kvp in _cameraMap)
|
||||
{
|
||||
if (kvp.Key)
|
||||
{
|
||||
kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
|
||||
}
|
||||
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_cameraMap.Clear();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_outlineResources != null && _renderers != null)
|
||||
{
|
||||
_camerasToRemove.Clear();
|
||||
|
||||
if (_updateRenderers)
|
||||
{
|
||||
_renderers.Reset(false, _ignoreLayerMask);
|
||||
}
|
||||
|
||||
foreach (var kvp in _cameraMap)
|
||||
{
|
||||
var camera = kvp.Key;
|
||||
var cmdBuffer = kvp.Value;
|
||||
|
||||
if (camera)
|
||||
{
|
||||
cmdBuffer.Clear();
|
||||
FillCommandBuffer(camera, cmdBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdBuffer.Dispose();
|
||||
_camerasToRemove.Add(camera);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var camera in _camerasToRemove)
|
||||
{
|
||||
_cameraMap.Remove(camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
CreateRenderersIfNeeded();
|
||||
CreateSettingsIfNeeded();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
if (_renderers != null)
|
||||
{
|
||||
_renderers.Reset(false, _ignoreLayerMask);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOutlineSettings
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Color OutlineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
return _outlineSettings.OutlineColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
_outlineSettings.OutlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int OutlineWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
return _outlineSettings.OutlineWidth;
|
||||
}
|
||||
set
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
_outlineSettings.OutlineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineIntensity
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
return _outlineSettings.OutlineIntensity;
|
||||
}
|
||||
set
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
_outlineSettings.OutlineIntensity = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineAlphaCutoff
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
return _outlineSettings.OutlineAlphaCutoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
_outlineSettings.OutlineAlphaCutoff = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OutlineRenderFlags OutlineRenderMode
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
return _outlineSettings.OutlineRenderMode;
|
||||
}
|
||||
set
|
||||
{
|
||||
CreateSettingsIfNeeded();
|
||||
_outlineSettings.OutlineRenderMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IOutlineSettings other)
|
||||
{
|
||||
return OutlineSettings.Equals(_outlineSettings, other);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
|
||||
private void OnCameraPreRender(Camera camera)
|
||||
{
|
||||
if (camera && (!_targetCamera || _targetCamera == camera))
|
||||
{
|
||||
if (_outlineSettings.RequiresCameraDepth)
|
||||
{
|
||||
camera.depthTextureMode |= DepthTextureMode.Depth;
|
||||
}
|
||||
|
||||
if (!_cameraMap.ContainsKey(camera))
|
||||
{
|
||||
var cmdBuf = new CommandBuffer();
|
||||
cmdBuf.name = string.Format("{0} - {1}", GetType().Name, name);
|
||||
camera.AddCommandBuffer(_cameraEvent, cmdBuf);
|
||||
|
||||
_cameraMap.Add(camera, cmdBuf);
|
||||
#if UNITY_EDITOR
|
||||
FillCommandBuffer(camera, cmdBuf);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FillCommandBuffer(Camera camera, CommandBuffer cmdBuffer)
|
||||
{
|
||||
if (_renderers.Count > 0)
|
||||
{
|
||||
using (var renderer = new OutlineRenderer(cmdBuffer, _outlineResources, camera.actualRenderingPath))
|
||||
{
|
||||
renderer.Render(_renderers.GetList(), _outlineSettings, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSettingsIfNeeded()
|
||||
{
|
||||
if (_outlineSettings == null)
|
||||
{
|
||||
_outlineSettings = new OutlineSettingsInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRenderersIfNeeded()
|
||||
{
|
||||
if (_renderers == null)
|
||||
{
|
||||
_renderers = new OutlineRendererCollection(gameObject);
|
||||
_renderers.Reset(false, _ignoreLayerMask);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 271c580db5fd384429cdac899152e9e0
|
||||
timeCreated: 1566149857
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _outlineResources: {fileID: 11400000, guid: d28e70f030b1a634db9a6a6d5478ef19,
|
||||
type: 2}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper behaviour for managing content of <see cref="OutlineLayerCollection"/> via Unity Editor.
|
||||
/// </summary>
|
||||
public sealed class OutlineBuilder : MonoBehaviour
|
||||
{
|
||||
#region data
|
||||
|
||||
[Serializable]
|
||||
internal class ContentItem
|
||||
{
|
||||
public GameObject Go;
|
||||
public int LayerIndex;
|
||||
}
|
||||
|
||||
#pragma warning disable 0649
|
||||
|
||||
[SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)]
|
||||
private OutlineLayerCollection _outlineLayers;
|
||||
[SerializeField, HideInInspector]
|
||||
private List<ContentItem> _content;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
internal List<ContentItem> Content { get => _content; set => _content = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of layers to manage.
|
||||
/// </summary>
|
||||
public OutlineLayerCollection OutlineLayers { get => _outlineLayers; set => _outlineLayers = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears content of all layers.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineLayers"/>
|
||||
public void Clear()
|
||||
{
|
||||
_outlineLayers?.ClearLayerContent();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_outlineLayers && _content != null)
|
||||
{
|
||||
foreach (var item in _content)
|
||||
{
|
||||
if (item.LayerIndex >= 0 && item.LayerIndex < _outlineLayers.Count && item.Go)
|
||||
{
|
||||
_outlineLayers.GetOrAddLayer(item.LayerIndex).Add(item.Go);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var effect = GetComponent<OutlineEffect>();
|
||||
|
||||
if (effect)
|
||||
{
|
||||
_outlineLayers = effect.OutlineLayersInternal;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_outlineLayers?.ClearLayerContent();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e746e776b0ae00d4a9d458b9430b95d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders outlines at specific camera. Should be attached to camera to function.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineLayer"/>
|
||||
/// <seealso cref="OutlineBehaviour"/>
|
||||
/// <seealso cref="OutlineSettings"/>
|
||||
/// <seealso href="https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/"/>
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public sealed partial class OutlineEffect : MonoBehaviour
|
||||
{
|
||||
#region data
|
||||
|
||||
[SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)]
|
||||
private OutlineResources _outlineResources;
|
||||
[SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)]
|
||||
private OutlineLayerCollection _outlineLayers;
|
||||
[SerializeField, HideInInspector]
|
||||
private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent;
|
||||
|
||||
private Camera _camera;
|
||||
private CommandBuffer _commandBuffer;
|
||||
private List<OutlineRenderObject> _renderObjects = new List<OutlineRenderObject>(16);
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets resources used by the effect implementation.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if setter argument is <see langword="null"/>.</exception>
|
||||
public OutlineResources OutlineResources
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineResources;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(OutlineResources));
|
||||
}
|
||||
|
||||
_outlineResources = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of outline layers.
|
||||
/// </summary>
|
||||
public OutlineLayerCollection OutlineLayers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineLayers;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineLayers = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets outline layers (for internal use only).
|
||||
/// </summary>
|
||||
internal OutlineLayerCollection OutlineLayersInternal => _outlineLayers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets <see cref="CameraEvent"/> used to render the outlines.
|
||||
/// </summary>
|
||||
public CameraEvent RenderEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cameraEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_cameraEvent != value)
|
||||
{
|
||||
if (_commandBuffer != null)
|
||||
{
|
||||
var camera = GetComponent<Camera>();
|
||||
|
||||
if (camera)
|
||||
{
|
||||
camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
|
||||
camera.AddCommandBuffer(value, _commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
_cameraEvent = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="GameObject"/> passed to the first outline layer. Creates the layer if needed.
|
||||
/// </summary>
|
||||
/// <param name="go">The <see cref="GameObject"/> to add and render outline for.</param>
|
||||
/// <seealso cref="AddGameObject(GameObject, int)"/>
|
||||
public void AddGameObject(GameObject go)
|
||||
{
|
||||
AddGameObject(go, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="GameObject"/> passed to the specified outline layer. Creates the layer if needed.
|
||||
/// </summary>
|
||||
/// <param name="go">The <see cref="GameObject"/> to add and render outline for.</param>
|
||||
/// <seealso cref="AddGameObject(GameObject)"/>
|
||||
public void AddGameObject(GameObject go, int layerIndex)
|
||||
{
|
||||
if (layerIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("layerIndex");
|
||||
}
|
||||
|
||||
CreateLayersIfNeeded();
|
||||
|
||||
while (_outlineLayers.Count <= layerIndex)
|
||||
{
|
||||
_outlineLayers.Add(new OutlineLayer());
|
||||
}
|
||||
|
||||
_outlineLayers[layerIndex].Add(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified <see cref="GameObject"/> from <see cref="OutlineLayers"/>.
|
||||
/// </summary>
|
||||
/// <param name="go">A <see cref="GameObject"/> to remove.</param>
|
||||
public void RemoveGameObject(GameObject go)
|
||||
{
|
||||
if (_outlineLayers)
|
||||
{
|
||||
_outlineLayers.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
OutlineResources.LogSrpNotSupported(this);
|
||||
OutlineResources.LogPpNotSupported(this);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InitCameraAndCommandBuffer();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ReleaseCameraAndCommandBuffer();
|
||||
}
|
||||
|
||||
private void OnPreRender()
|
||||
{
|
||||
FillCommandBuffer();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// TODO: Find a way to do this once per OutlineLayerCollection instance.
|
||||
if (_outlineLayers)
|
||||
{
|
||||
_outlineLayers.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
//private void OnValidate()
|
||||
//{
|
||||
// InitCameraAndCommandBuffer();
|
||||
// FillCommandBuffer();
|
||||
//}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_outlineLayers = null;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
|
||||
private void InitCameraAndCommandBuffer()
|
||||
{
|
||||
_camera = GetComponent<Camera>();
|
||||
|
||||
if (_camera && _commandBuffer is null)
|
||||
{
|
||||
_commandBuffer = new CommandBuffer
|
||||
{
|
||||
name = string.Format("{0} - {1}", GetType().Name, name)
|
||||
};
|
||||
|
||||
_camera.depthTextureMode |= DepthTextureMode.Depth;
|
||||
_camera.AddCommandBuffer(_cameraEvent, _commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseCameraAndCommandBuffer()
|
||||
{
|
||||
if (_commandBuffer != null)
|
||||
{
|
||||
if (_camera)
|
||||
{
|
||||
_camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
|
||||
}
|
||||
|
||||
_commandBuffer.Dispose();
|
||||
_commandBuffer = null;
|
||||
}
|
||||
|
||||
_camera = null;
|
||||
}
|
||||
|
||||
private void FillCommandBuffer()
|
||||
{
|
||||
if (_camera && _outlineLayers && _commandBuffer != null)
|
||||
{
|
||||
_commandBuffer.Clear();
|
||||
|
||||
if (_outlineResources && _outlineResources.IsValid)
|
||||
{
|
||||
using (var renderer = new OutlineRenderer(_commandBuffer, _outlineResources, _camera.actualRenderingPath))
|
||||
{
|
||||
_renderObjects.Clear();
|
||||
_outlineLayers.GetRenderObjects(_renderObjects);
|
||||
renderer.Render(_renderObjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateLayersIfNeeded()
|
||||
{
|
||||
if (_outlineLayers is null)
|
||||
{
|
||||
_outlineLayers = ScriptableObject.CreateInstance<OutlineLayerCollection>();
|
||||
_outlineLayers.name = "OutlineLayers";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 270d3185d159bf54fb4cddbb42235437
|
||||
timeCreated: 1566149591
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _outlineResources: {fileID: 11400000, guid: d28e70f030b1a634db9a6a6d5478ef19,
|
||||
type: 2}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
internal enum OutlineFilterMode
|
||||
{
|
||||
None,
|
||||
UseLayerMask,
|
||||
UseRenderingLayerMask,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82c9d42cc303be24d852b8db7c4b650f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,509 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of <see cref="GameObject"/> instances that share outline settings. An <see cref="OutlineLayer"/>
|
||||
/// can only belong to one <see cref="OutlineLayerCollection"/> at time.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineLayerCollection"/>
|
||||
/// <seealso cref="OutlineEffect"/>
|
||||
[Serializable]
|
||||
public sealed class OutlineLayer : ICollection<GameObject>, IReadOnlyCollection<GameObject>, IOutlineSettings
|
||||
{
|
||||
#region data
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineSettingsInstance _settings = new OutlineSettingsInstance();
|
||||
[SerializeField, HideInInspector]
|
||||
private string _name;
|
||||
[SerializeField, HideInInspector]
|
||||
private bool _enabled = true;
|
||||
[SerializeField, HideInInspector]
|
||||
private bool _mergeLayerObjects;
|
||||
|
||||
private OutlineLayerCollection _parentCollection;
|
||||
private Dictionary<GameObject, OutlineRendererCollection> _outlineObjects = new Dictionary<GameObject, OutlineRendererCollection>();
|
||||
private List<Renderer> _mergedRenderers;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_name))
|
||||
{
|
||||
return "OutlineLayer #" + Index.ToString();
|
||||
}
|
||||
|
||||
return _name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the layer is enabled.
|
||||
/// </summary>
|
||||
/// <seealso cref="Priority"/>
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return _enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether layer game objects should be trated as one.
|
||||
/// </summary>
|
||||
public bool MergeLayerObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mergeLayerObjects;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mergeLayerObjects = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets index of the layer in parent collection.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parentCollection != null)
|
||||
{
|
||||
return _parentCollection.IndexOf(this);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets outline settings. Set this to non-<see langword="null"/> value to share settings with other components.
|
||||
/// </summary>
|
||||
public OutlineSettings OutlineSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineSettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineSettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineLayer"/> class.
|
||||
/// </summary>
|
||||
public OutlineLayer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineLayer"/> class.
|
||||
/// </summary>
|
||||
internal OutlineLayer(OutlineLayerCollection parentCollection)
|
||||
{
|
||||
_parentCollection = parentCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineLayer"/> class.
|
||||
/// </summary>
|
||||
public OutlineLayer(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineLayer"/> class.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="settings"/> is <see langword="null"/>.</exception>
|
||||
public OutlineLayer(OutlineSettings settings)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
_settings.OutlineSettings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineLayer"/> class.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="settings"/> is <see langword="null"/>.</exception>
|
||||
public OutlineLayer(string name, OutlineSettings settings)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
_name = name;
|
||||
_settings.OutlineSettings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get renderers assosiated with the specified <see cref="GameObject"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="go"/> is <see langword="null"/>.</exception>
|
||||
public bool TryGetRenderers(GameObject go, out ICollection<Renderer> renderers)
|
||||
{
|
||||
if (go is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(go));
|
||||
}
|
||||
|
||||
if (_outlineObjects.TryGetValue(go, out var result))
|
||||
{
|
||||
renderers = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
renderers = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the objects for rendering.
|
||||
/// </summary>
|
||||
public void GetRenderObjects(IList<OutlineRenderObject> renderObjects)
|
||||
{
|
||||
if (_enabled)
|
||||
{
|
||||
if (_mergeLayerObjects)
|
||||
{
|
||||
renderObjects.Add(new OutlineRenderObject(GetRenderers(), this, Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kvp in _outlineObjects)
|
||||
{
|
||||
var go = kvp.Key;
|
||||
|
||||
if (go && go.activeInHierarchy)
|
||||
{
|
||||
renderObjects.Add(new OutlineRenderObject(kvp.Value.GetList(), _settings, go.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all layer renderers.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Renderer> GetRenderers()
|
||||
{
|
||||
if (_enabled)
|
||||
{
|
||||
if (_mergedRenderers != null)
|
||||
{
|
||||
_mergedRenderers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_mergedRenderers = new List<Renderer>();
|
||||
}
|
||||
|
||||
foreach (var kvp in _outlineObjects)
|
||||
{
|
||||
var go = kvp.Key;
|
||||
|
||||
if (go && go.activeInHierarchy)
|
||||
{
|
||||
var rl = kvp.Value.GetList();
|
||||
|
||||
for (var i = 0; i < rl.Count; i++)
|
||||
{
|
||||
_mergedRenderers.Add(rl[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _mergedRenderers;
|
||||
}
|
||||
|
||||
return Array.Empty<Renderer>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region internals
|
||||
|
||||
internal string NameTag
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal OutlineLayerCollection ParentCollection => _parentCollection;
|
||||
|
||||
internal void UpdateRenderers(int ignoreLayers)
|
||||
{
|
||||
foreach (var renderers in _outlineObjects.Values)
|
||||
{
|
||||
renderers.Reset(false, ignoreLayers);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_outlineObjects.Clear();
|
||||
}
|
||||
|
||||
internal void SetCollection(OutlineLayerCollection collection)
|
||||
{
|
||||
if (_parentCollection == null || collection == null || _parentCollection == collection)
|
||||
{
|
||||
_parentCollection = collection;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("OutlineLayer can only belong to a single OutlineLayerCollection.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOutlineSettings
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Color OutlineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int OutlineWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineWidth;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineIntensity
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineIntensity;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineIntensity = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineAlphaCutoff
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineAlphaCutoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineAlphaCutoff = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OutlineRenderFlags OutlineRenderMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.OutlineRenderMode;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settings.OutlineRenderMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Count => _outlineObjects.Count;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Add(GameObject go)
|
||||
{
|
||||
if (go is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(go));
|
||||
}
|
||||
|
||||
if (!_outlineObjects.ContainsKey(go))
|
||||
{
|
||||
var renderers = new OutlineRendererCollection(go);
|
||||
renderers.Reset(false, _parentCollection.IgnoreLayerMask);
|
||||
_outlineObjects.Add(go, renderers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Remove(GameObject go)
|
||||
{
|
||||
if (go is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _outlineObjects.Remove(go);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Contains(GameObject go)
|
||||
{
|
||||
if (go is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _outlineObjects.ContainsKey(go);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
_outlineObjects.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyTo(GameObject[] array, int arrayIndex)
|
||||
{
|
||||
_outlineObjects.Keys.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<GameObject> GetEnumerator()
|
||||
{
|
||||
return _outlineObjects.Keys.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _outlineObjects.Keys.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IOutlineSettings other)
|
||||
{
|
||||
return OutlineSettings.Equals(this, other);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
|
||||
if (string.IsNullOrEmpty(_name))
|
||||
{
|
||||
text.Append("OutlineLayer");
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Append(_name);
|
||||
}
|
||||
|
||||
if (_parentCollection != null)
|
||||
{
|
||||
text.Append(" #");
|
||||
text.Append(_parentCollection.IndexOf(this));
|
||||
}
|
||||
|
||||
if (_outlineObjects.Count > 0)
|
||||
{
|
||||
text.Append(" (");
|
||||
|
||||
foreach (var go in _outlineObjects.Keys)
|
||||
{
|
||||
text.Append(go.name);
|
||||
text.Append(", ");
|
||||
}
|
||||
|
||||
text.Remove(text.Length - 2, 2);
|
||||
text.Append(")");
|
||||
}
|
||||
|
||||
return string.Format("{0}", text);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return OutlineSettings.Equals(this, other as IOutlineSettings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1360e19784ddfac45a7dcb6ba39595ed
|
||||
timeCreated: 1566130871
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,320 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// A serializable collection of outline layers.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineLayer"/>
|
||||
/// <seealso cref="OutlineEffect"/>
|
||||
/// <seealso cref="OutlineSettings"/>
|
||||
[CreateAssetMenu(fileName = "OutlineLayerCollection", menuName = "UnityFx/Outline/Outline Layer Collection")]
|
||||
public sealed class OutlineLayerCollection : ScriptableObject, IList<OutlineLayer>, IReadOnlyList<OutlineLayer>
|
||||
{
|
||||
#region data
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
private List<OutlineLayer> _layers = new List<OutlineLayer>();
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
private int _ignoreLayerMask;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets layer mask to use for ignored <see cref="Renderer"/> components in layer game objects.
|
||||
/// </summary>
|
||||
public int IgnoreLayerMask
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ignoreLayerMask;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_ignoreLayerMask != value)
|
||||
{
|
||||
_ignoreLayerMask = value;
|
||||
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.UpdateRenderers(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of game objects in the layers.
|
||||
/// </summary>
|
||||
public int NumberOfObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
result += layer.Count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a layer with the specified index. If layer at the <paramref name="index"/> does not exist, creates one.
|
||||
/// </summary>
|
||||
public OutlineLayer GetOrAddLayer(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
while (index >= _layers.Count)
|
||||
{
|
||||
_layers.Add(new OutlineLayer(this));
|
||||
}
|
||||
|
||||
return _layers[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new layer.
|
||||
/// </summary>
|
||||
public OutlineLayer AddLayer()
|
||||
{
|
||||
var layer = new OutlineLayer(this);
|
||||
_layers.Add(layer);
|
||||
return layer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the objects for rendering.
|
||||
/// </summary>
|
||||
public void GetRenderObjects(IList<OutlineRenderObject> renderObjects)
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.GetRenderObjects(renderObjects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified <see cref="GameObject"/> from layers.
|
||||
/// </summary>
|
||||
/// <param name="go">A <see cref="GameObject"/> to remove.</param>
|
||||
public void Remove(GameObject go)
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
if (layer.Remove(go))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all game objects registered in layers.
|
||||
/// </summary>
|
||||
public void ClearLayerContent()
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region internals
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScriptableObject
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.Clear();
|
||||
layer.SetCollection(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OutlineLayer this[int layerIndex]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _layers[layerIndex];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException("layer");
|
||||
}
|
||||
|
||||
if (layerIndex < 0 || layerIndex >= _layers.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(layerIndex));
|
||||
}
|
||||
|
||||
if (_layers[layerIndex] != value)
|
||||
{
|
||||
value.SetCollection(this);
|
||||
|
||||
_layers[layerIndex].SetCollection(null);
|
||||
_layers[layerIndex] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(OutlineLayer layer)
|
||||
{
|
||||
if (layer != null)
|
||||
{
|
||||
return _layers.IndexOf(layer);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Insert(int index, OutlineLayer layer)
|
||||
{
|
||||
if (layer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layer));
|
||||
}
|
||||
|
||||
if (layer.ParentCollection != this)
|
||||
{
|
||||
layer.SetCollection(this);
|
||||
_layers.Insert(index, layer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index >= 0 && index < _layers.Count)
|
||||
{
|
||||
_layers[index].SetCollection(null);
|
||||
_layers.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Count => _layers.Count;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Add(OutlineLayer layer)
|
||||
{
|
||||
if (layer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layer));
|
||||
}
|
||||
|
||||
if (layer.ParentCollection != this)
|
||||
{
|
||||
layer.SetCollection(this);
|
||||
_layers.Add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Remove(OutlineLayer layer)
|
||||
{
|
||||
if (_layers.Remove(layer))
|
||||
{
|
||||
layer.SetCollection(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
if (_layers.Count > 0)
|
||||
{
|
||||
foreach (var layer in _layers)
|
||||
{
|
||||
layer.SetCollection(null);
|
||||
}
|
||||
|
||||
_layers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Contains(OutlineLayer layer)
|
||||
{
|
||||
if (layer is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _layers.Contains(layer);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyTo(OutlineLayer[] array, int arrayIndex)
|
||||
{
|
||||
_layers.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<OutlineLayer> GetEnumerator()
|
||||
{
|
||||
return _layers.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _layers.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57d0c11168277cf4eb3b4b89706e6aa5
|
||||
timeCreated: 1566560091
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,532 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// This asset is used to store references to shaders and other resources needed at runtime without having to use a Resources folder.
|
||||
/// </summary>
|
||||
/// <seealso cref="OutlineRenderer"/>
|
||||
[CreateAssetMenu(fileName = "OutlineResources", menuName = "UnityFx/Outline/Outline Resources")]
|
||||
public sealed class OutlineResources : ScriptableObject
|
||||
{
|
||||
#region data
|
||||
|
||||
[SerializeField]
|
||||
private Shader _renderShader;
|
||||
[SerializeField]
|
||||
private Shader _outlineShader;
|
||||
|
||||
private Material _renderMaterial;
|
||||
private Material _outlineMaterial;
|
||||
private MaterialPropertyBlock _props;
|
||||
private Mesh _fullscreenTriangleMesh;
|
||||
private float[][] _gaussSamples;
|
||||
private bool _useDrawMesh;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Minimum value of outline width parameter.
|
||||
/// </summary>
|
||||
/// <seealso cref="MaxWidth"/>
|
||||
public const int MinWidth = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum value of outline width parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the value is changed here, it should be adjusted in Outline.shader as well.
|
||||
/// </remarks>
|
||||
/// <seealso cref="MinWidth"/>
|
||||
public const int MaxWidth = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum value of outline intensity parameter.
|
||||
/// </summary>
|
||||
/// <seealso cref="MaxIntensity"/>
|
||||
/// <seealso cref="SolidIntensity"/>
|
||||
public const int MinIntensity = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum value of outline intensity parameter.
|
||||
/// </summary>
|
||||
/// <seealso cref="MinIntensity"/>
|
||||
/// <seealso cref="SolidIntensity"/>
|
||||
public const int MaxIntensity = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Value of outline intensity parameter that is treated as solid fill.
|
||||
/// </summary>
|
||||
/// <seealso cref="MinIntensity"/>
|
||||
/// <seealso cref="MaxIntensity"/>
|
||||
public const int SolidIntensity = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum value of outline alpha cutoff parameter.
|
||||
/// </summary>
|
||||
/// <seealso cref="MaxAlphaCutoff"/>
|
||||
public const float MinAlphaCutoff = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum value of outline alpha cutoff parameter.
|
||||
/// </summary>
|
||||
/// <seealso cref="MinAlphaCutoff"/>
|
||||
public const float MaxAlphaCutoff = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Name of _MainTex shader parameter.
|
||||
/// </summary>
|
||||
public const string MainTexName = "_MainTex";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _MaskTex shader parameter.
|
||||
/// </summary>
|
||||
public const string MaskTexName = "_MaskTex";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _TempTex shader parameter.
|
||||
/// </summary>
|
||||
public const string TempTexName = "_TempTex";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _Color shader parameter.
|
||||
/// </summary>
|
||||
public const string ColorName = "_Color";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _Width shader parameter.
|
||||
/// </summary>
|
||||
public const string WidthName = "_Width";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _Intensity shader parameter.
|
||||
/// </summary>
|
||||
public const string IntensityName = "_Intensity";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _Cutoff shader parameter.
|
||||
/// </summary>
|
||||
public const string AlphaCutoffName = "_Cutoff";
|
||||
|
||||
/// <summary>
|
||||
/// Name of _GaussSamples shader parameter.
|
||||
/// </summary>
|
||||
public const string GaussSamplesName = "_GaussSamples";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the _USE_DRAWMESH shader feature.
|
||||
/// </summary>
|
||||
public const string UseDrawMeshFeatureName = "_USE_DRAWMESH";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the outline effect.
|
||||
/// </summary>
|
||||
public const string EffectName = "Outline";
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip text for <see cref="OutlineResources"/> field.
|
||||
/// </summary>
|
||||
public const string OutlineResourcesTooltip = "Outline resources to use (shaders, materials etc). Do not change defaults unless you know what you're doing.";
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip text for <see cref="OutlineLayerCollection"/> field.
|
||||
/// </summary>
|
||||
public const string OutlineLayerCollectionTooltip = "Collection of outline layers to use. This can be used to share outline settings between multiple cameras.";
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip text for outline <see cref="LayerMask"/> field.
|
||||
/// </summary>
|
||||
public const string OutlineLayerMaskTooltip = "Layer mask for outined objects.";
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip text for outline <see cref="LayerMask"/> field.
|
||||
/// </summary>
|
||||
public const string OutlineRenderingLayerMaskTooltip = "Rendering layer mask for outined objects.";
|
||||
|
||||
/// <summary>
|
||||
/// Index of the default pass in <see cref="RenderShader"/>.
|
||||
/// </summary>
|
||||
public const int RenderShaderDefaultPassId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the alpha-test pass in <see cref="RenderShader"/>.
|
||||
/// </summary>
|
||||
public const int RenderShaderAlphaTestPassId = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the HPass in <see cref="OutlineShader"/>.
|
||||
/// </summary>
|
||||
public const int OutlineShaderHPassId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the VPass in <see cref="OutlineShader"/>.
|
||||
/// </summary>
|
||||
public const int OutlineShaderVPassId = 1;
|
||||
|
||||
/// <summary>
|
||||
/// SRP not supported message.
|
||||
/// </summary>
|
||||
internal const string SrpNotSupported = "{0} works with built-in render pipeline only. It does not support SRP (including URP and HDRP).";
|
||||
|
||||
/// <summary>
|
||||
/// Post-processing not supported message.
|
||||
/// </summary>
|
||||
internal const string PpNotSupported = "{0} does not support Unity Post-processing stack v2. It might not work as expected.";
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _MainTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly int MainTexId = Shader.PropertyToID(MainTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Texture identifier for _MainTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly RenderTargetIdentifier MainTex = new RenderTargetIdentifier(MainTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _MaskTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly int MaskTexId = Shader.PropertyToID(MaskTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Texture identifier for _MaskTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly RenderTargetIdentifier MaskTex = new RenderTargetIdentifier(MaskTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _TempTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly int TempTexId = Shader.PropertyToID(TempTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Texture identifier for _TempTex shader parameter.
|
||||
/// </summary>
|
||||
public readonly RenderTargetIdentifier TempTex = new RenderTargetIdentifier(TempTexName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _Color shader parameter.
|
||||
/// </summary>
|
||||
public readonly int ColorId = Shader.PropertyToID(ColorName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _Width shader parameter.
|
||||
/// </summary>
|
||||
public readonly int WidthId = Shader.PropertyToID(WidthName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _Intensity shader parameter.
|
||||
/// </summary>
|
||||
public readonly int IntensityId = Shader.PropertyToID(IntensityName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _Cutoff shader parameter.
|
||||
/// </summary>
|
||||
public readonly int AlphaCutoffId = Shader.PropertyToID(AlphaCutoffName);
|
||||
|
||||
/// <summary>
|
||||
/// Hashed name of _GaussSamples shader parameter.
|
||||
/// </summary>
|
||||
public readonly int GaussSamplesId = Shader.PropertyToID(GaussSamplesName);
|
||||
|
||||
/// <summary>
|
||||
/// Temp materials list. Used by <see cref="OutlineRenderer"/> to avoid GC allocations.
|
||||
/// </summary>
|
||||
internal readonly List<Material> TmpMaterials = new List<Material>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Shader"/> that renders objects outlined with a solid while color.
|
||||
/// </summary>
|
||||
public Shader RenderShader
|
||||
{
|
||||
get
|
||||
{
|
||||
return _renderShader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Shader"/> that renders outline around the mask, that was generated with <see cref="RenderShader"/>.
|
||||
/// </summary>
|
||||
public Shader OutlineShader
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineShader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="RenderShader"/>-based material.
|
||||
/// </summary>
|
||||
public Material RenderMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_renderMaterial == null)
|
||||
{
|
||||
UnityEngine.Debug.Assert(_renderShader != null, "No RenderShader is set in outline resources.", this);
|
||||
|
||||
_renderMaterial = new Material(_renderShader)
|
||||
{
|
||||
name = "Outline - RenderColor",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
}
|
||||
|
||||
return _renderMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="OutlineShader"/>-based material.
|
||||
/// </summary>
|
||||
public Material OutlineMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_outlineMaterial == null)
|
||||
{
|
||||
UnityEngine.Debug.Assert(_outlineShader != null, "No OutlineShader is set in outline resources.", this);
|
||||
|
||||
_outlineMaterial = new Material(_outlineShader)
|
||||
{
|
||||
name = "Outline - Main",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
if (_useDrawMesh)
|
||||
{
|
||||
_outlineMaterial.EnableKeyword(UseDrawMeshFeatureName);
|
||||
}
|
||||
}
|
||||
|
||||
return _outlineMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="MaterialPropertyBlock"/> for <see cref="VPassBlendMaterial"/>.
|
||||
/// </summary>
|
||||
public MaterialPropertyBlock Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_props is null)
|
||||
{
|
||||
_props = new MaterialPropertyBlock();
|
||||
}
|
||||
|
||||
return _props;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a fullscreen triangle mesh. The mesh is lazy-initialized on the first access.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used by <see cref="OutlineRenderer"/> to avoid Blit() calls and use DrawMesh() passing
|
||||
/// this mesh as the first argument. When running on a device with Shader Model 3.5 support this
|
||||
/// should not be used at all, as the vertices are generated in vertex shader with DrawProcedural() call.
|
||||
/// </remarks>
|
||||
/// <seealso cref="OutlineRenderer"/>
|
||||
public Mesh FullscreenTriangleMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fullscreenTriangleMesh == null)
|
||||
{
|
||||
_fullscreenTriangleMesh = new Mesh()
|
||||
{
|
||||
name = "Outline - FullscreenTriangle",
|
||||
hideFlags = HideFlags.HideAndDontSave,
|
||||
vertices = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(3, -1, 0), new Vector3(-1, 3, 0) },
|
||||
triangles = new int[] { 0, 1, 2 }
|
||||
};
|
||||
|
||||
_fullscreenTriangleMesh.UploadMeshData(true);
|
||||
}
|
||||
|
||||
return _fullscreenTriangleMesh;
|
||||
}
|
||||
set
|
||||
{
|
||||
_fullscreenTriangleMesh = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether <see cref="FullscreenTriangleMesh"/> is used for image effects rendering even when procedural rendering is available.
|
||||
/// </summary>
|
||||
public bool UseFullscreenTriangleMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
return _useDrawMesh;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_useDrawMesh != value)
|
||||
{
|
||||
_useDrawMesh = value;
|
||||
|
||||
if (_outlineMaterial)
|
||||
{
|
||||
if (_useDrawMesh)
|
||||
{
|
||||
_outlineMaterial.EnableKeyword(UseDrawMeshFeatureName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_outlineMaterial.DisableKeyword(UseDrawMeshFeatureName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the instance is in valid state.
|
||||
/// </summary>
|
||||
public bool IsValid => RenderShader && OutlineShader;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="MaterialPropertyBlock"/> instance initialized with values from <paramref name="settings"/>.
|
||||
/// </summary>
|
||||
public MaterialPropertyBlock GetProperties(IOutlineSettings settings)
|
||||
{
|
||||
if (_props is null)
|
||||
{
|
||||
_props = new MaterialPropertyBlock();
|
||||
}
|
||||
|
||||
_props.SetFloat(WidthId, settings.OutlineWidth);
|
||||
_props.SetColor(ColorId, settings.OutlineColor);
|
||||
|
||||
if ((settings.OutlineRenderMode & OutlineRenderFlags.Blurred) != 0)
|
||||
{
|
||||
_props.SetFloat(IntensityId, settings.OutlineIntensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_props.SetFloat(IntensityId, SolidIntensity);
|
||||
}
|
||||
|
||||
return _props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets cached gauss samples for the specified outline <paramref name="width"/>.
|
||||
/// </summary>
|
||||
public float[] GetGaussSamples(int width)
|
||||
{
|
||||
var index = Mathf.Clamp(width, 1, MaxWidth) - 1;
|
||||
|
||||
if (_gaussSamples is null)
|
||||
{
|
||||
_gaussSamples = new float[MaxWidth][];
|
||||
}
|
||||
|
||||
if (_gaussSamples[index] is null)
|
||||
{
|
||||
_gaussSamples[index] = GetGaussSamples(width, null);
|
||||
}
|
||||
|
||||
return _gaussSamples[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resources to defaults.
|
||||
/// </summary>
|
||||
public void ResetToDefaults()
|
||||
{
|
||||
_renderShader = Shader.Find("Hidden/UnityFx/OutlineColor");
|
||||
_outlineShader = Shader.Find("Hidden/UnityFx/Outline");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates value of Gauss function for the specified <paramref name="x"/> and <paramref name="stdDev"/> values.
|
||||
/// </summary>
|
||||
/// <seealso href="https://en.wikipedia.org/wiki/Gaussian_blur"/>
|
||||
/// <seealso href="https://en.wikipedia.org/wiki/Normal_distribution"/>
|
||||
public static float Gauss(float x, float stdDev)
|
||||
{
|
||||
var stdDev2 = stdDev * stdDev * 2;
|
||||
var a = 1 / Mathf.Sqrt(Mathf.PI * stdDev2);
|
||||
var gauss = a * Mathf.Pow((float)Math.E, -x * x / stdDev2);
|
||||
|
||||
return gauss;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples Gauss function for the specified <paramref name="width"/>.
|
||||
/// </summary>
|
||||
/// <seealso href="https://en.wikipedia.org/wiki/Normal_distribution"/>
|
||||
public static float[] GetGaussSamples(int width, float[] samples)
|
||||
{
|
||||
// NOTE: According to '3 sigma' rule there is no reason to have StdDev less then width / 3.
|
||||
// In practice blur looks best when StdDev is within range [width / 3, width / 2].
|
||||
var stdDev = width * 0.5f;
|
||||
|
||||
if (samples is null)
|
||||
{
|
||||
samples = new float[MaxWidth];
|
||||
}
|
||||
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
samples[i] = Gauss(i, stdDev);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a console warning if SRP is detected.
|
||||
/// </summary>
|
||||
public static void LogSrpNotSupported(UnityEngine.Object obj)
|
||||
{
|
||||
if (GraphicsSettings.renderPipelineAsset)
|
||||
{
|
||||
UnityEngine.Debug.LogWarningFormat(obj, SrpNotSupported, obj.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a console warning if Post Processing Stack v2 is detected.
|
||||
/// </summary>
|
||||
[Conditional("UNITY_POST_PROCESSING_STACK_V2")]
|
||||
public static void LogPpNotSupported(UnityEngine.Object obj)
|
||||
{
|
||||
UnityEngine.Debug.LogWarningFormat(obj, PpNotSupported, obj.GetType().Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScriptableObject
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_renderMaterial)
|
||||
{
|
||||
_renderMaterial.shader = _renderShader;
|
||||
}
|
||||
|
||||
if (_outlineMaterial)
|
||||
{
|
||||
_outlineMaterial.shader = _outlineShader;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b503341e0a514e3489c4851727e68257
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _renderShader: {fileID: 4800000, guid: ac20fbf75bafe454aba5ef3c098349df, type: 3}
|
||||
- _outlineShader: {fileID: 4800000, guid: 41c9acbf41c8245498ac9beab378de12, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Outline settings.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "OutlineSettings", menuName = "UnityFx/Outline/Outline Settings")]
|
||||
public sealed class OutlineSettings : ScriptableObject, IOutlineSettings
|
||||
{
|
||||
#region data
|
||||
|
||||
// NOTE: There is a custom editor for OutlineSettings, so no need to show these in default inspector.
|
||||
[SerializeField, HideInInspector]
|
||||
private Color _outlineColor = Color.red;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinWidth, OutlineResources.MaxWidth)]
|
||||
private int _outlineWidth = 4;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinIntensity, OutlineResources.MaxIntensity)]
|
||||
private float _outlineIntensity = 2;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinAlphaCutoff, OutlineResources.MaxAlphaCutoff)]
|
||||
private float _outlineAlphaCutoff = 0.9f;
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineRenderFlags _outlineMode;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
public static bool Equals(IOutlineSettings lhs, IOutlineSettings rhs)
|
||||
{
|
||||
if (lhs == null || rhs == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs.OutlineColor == rhs.OutlineColor &&
|
||||
lhs.OutlineWidth == rhs.OutlineWidth &&
|
||||
lhs.OutlineRenderMode == rhs.OutlineRenderMode &&
|
||||
Mathf.Approximately(lhs.OutlineIntensity, rhs.OutlineIntensity) &&
|
||||
Mathf.Approximately(lhs.OutlineAlphaCutoff, rhs.OutlineAlphaCutoff);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOutlineSettings
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Color OutlineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int OutlineWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineWidth;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineWidth = Mathf.Clamp(value, OutlineResources.MinWidth, OutlineResources.MaxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineIntensity
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineIntensity;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineIntensity = Mathf.Clamp(value, OutlineResources.MinIntensity, OutlineResources.MaxIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineAlphaCutoff
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineAlphaCutoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineAlphaCutoff = Mathf.Clamp(value, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OutlineRenderFlags OutlineRenderMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineMode;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(IOutlineSettings other)
|
||||
{
|
||||
return Equals(this, other);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return Equals(this, other as IOutlineSettings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b579424fd3338724cba3155ee4d53475
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IOutlineSettings"/>.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class OutlineSettingsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether outline should use alpha testing.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsDepthTestingEnabled(IOutlineSettings)"/>
|
||||
/// <seealso cref="IsBlurEnabled(IOutlineSettings)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAlphaTestingEnabled(this IOutlineSettings settings)
|
||||
{
|
||||
return (settings.OutlineRenderMode & OutlineRenderFlags.EnableAlphaTesting) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether outline should use depth testing.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsAlphaTestingEnabled(IOutlineSettings)"/>
|
||||
/// <seealso cref="IsBlurEnabled(IOutlineSettings)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDepthTestingEnabled(this IOutlineSettings settings)
|
||||
{
|
||||
return (settings.OutlineRenderMode & OutlineRenderFlags.EnableDepthTesting) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether outline frame should be blurred.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsAlphaTestingEnabled(IOutlineSettings)"/>
|
||||
/// <seealso cref="IsDepthTestingEnabled(IOutlineSettings)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsBlurEnabled(this IOutlineSettings settings)
|
||||
{
|
||||
return (settings.OutlineRenderMode & OutlineRenderFlags.Blurred) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1faa9de2a3d5a374b84983eae45fc559
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
[Serializable]
|
||||
internal class OutlineSettingsInstance : IOutlineSettings
|
||||
{
|
||||
#region data
|
||||
|
||||
#pragma warning disable 0649
|
||||
|
||||
// NOTE: There are custom editors for public components, so no need to show these in default inspector.
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineSettings _outlineSettings;
|
||||
[SerializeField, HideInInspector]
|
||||
private Color _outlineColor = Color.red;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinWidth, OutlineResources.MaxWidth)]
|
||||
private int _outlineWidth = 4;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinIntensity, OutlineResources.MaxIntensity)]
|
||||
private float _outlineIntensity = 2;
|
||||
[SerializeField, HideInInspector, Range(OutlineResources.MinAlphaCutoff, OutlineResources.MaxAlphaCutoff)]
|
||||
private float _outlineAlphaCutoff = 0.9f;
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineRenderFlags _outlineMode;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
public bool RequiresCameraDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return (OutlineRenderMode & OutlineRenderFlags.EnableDepthTesting) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public OutlineSettings OutlineSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineSettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IOutlineSettings
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Color OutlineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings is null ? _outlineColor : _outlineSettings.OutlineColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int OutlineWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings is null ? _outlineWidth : _outlineSettings.OutlineWidth;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineWidth = Mathf.Clamp(value, OutlineResources.MinWidth, OutlineResources.MaxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineIntensity
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings is null ? _outlineIntensity : _outlineSettings.OutlineIntensity;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineIntensity = Mathf.Clamp(value, OutlineResources.MinIntensity, OutlineResources.MaxIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float OutlineAlphaCutoff
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings is null ? _outlineAlphaCutoff : _outlineSettings.OutlineAlphaCutoff;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineAlphaCutoff = Mathf.Clamp(value, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OutlineRenderFlags OutlineRenderMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _outlineSettings is null ? _outlineMode : _outlineSettings.OutlineRenderMode;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outlineMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
public bool Equals(IOutlineSettings other)
|
||||
{
|
||||
return OutlineSettings.Equals(this, other);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ea4c60e473b8ef4790934bb274993cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
[Serializable]
|
||||
internal class OutlineSettingsWithLayerMask : OutlineSettingsInstance
|
||||
{
|
||||
#region data
|
||||
|
||||
#pragma warning disable 0649
|
||||
|
||||
// NOTE: There are custom editors for public components, so no need to show these in default inspector.
|
||||
[SerializeField, HideInInspector]
|
||||
private OutlineFilterMode _filterMode;
|
||||
[SerializeField, HideInInspector]
|
||||
private LayerMask _layerMask;
|
||||
[SerializeField, HideInInspector]
|
||||
private uint _renderingLayerMask = 1;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
public int OutlineLayerMask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filterMode == OutlineFilterMode.UseLayerMask)
|
||||
{
|
||||
return _layerMask;
|
||||
}
|
||||
|
||||
if (_filterMode == OutlineFilterMode.UseRenderingLayerMask)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public uint OutlineRenderingLayerMask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filterMode == OutlineFilterMode.UseLayerMask)
|
||||
{
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
if (_filterMode == OutlineFilterMode.UseRenderingLayerMask)
|
||||
{
|
||||
return _renderingLayerMask;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f6645ede9c6d2346b6aee185f8261d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bd10545b6de6654b864faecdec920cd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("UnityFx.Outline")]
|
||||
[assembly: AssemblyProduct("UnityFx.Outline")]
|
||||
[assembly: AssemblyDescription("Screen-space outlines for Unity3d.")]
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyCopyright("Copyright © Alexander Bogarsukov 2019-2020")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// Make internals visible to the editor assembly.
|
||||
[assembly: InternalsVisibleTo("UnityFx.Outline.Editor")]
|
||||
[assembly: InternalsVisibleTo("UnityFx.Outline.URP")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1613c034178676349be3282789167284
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
#if !UNITY_2018_4_OR_NEWER
|
||||
#error UnityFx.Outline requires Unity 2018.4 or newer.
|
||||
#endif
|
||||
|
||||
#if NET_LEGACY || NET_2_0 || NET_2_0_SUBSET
|
||||
#error UnityFx.Outline does not support .NET 3.5. Please set Scripting Runtime Version to .NET 4.x Equivalent in Unity Player Settings.
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 955bb53eefa37054cb49969575341469
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 263f9a02e31427d4d9d910267274bfa0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates outline render modes.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum OutlineRenderFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Outline frame is a solid line.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Outline frame is blurred.
|
||||
/// </summary>
|
||||
Blurred = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Enables depth testing when rendering object outlines. Only visible parts of objects are outlined.
|
||||
/// </summary>
|
||||
EnableDepthTesting = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Enabled alpha testing when rendering outlines.
|
||||
/// </summary>
|
||||
EnableAlphaTesting = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 836bd13bd33c59246b1cebab92f8e62a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// A single outline object + its outline settings.
|
||||
/// </summary>
|
||||
public readonly struct OutlineRenderObject : IEquatable<OutlineRenderObject>
|
||||
{
|
||||
#region data
|
||||
|
||||
private readonly string _tag;
|
||||
private readonly IReadOnlyList<Renderer> _renderers;
|
||||
private readonly IOutlineSettings _outlineSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object tag name.
|
||||
/// </summary>
|
||||
public string Tag => _tag;
|
||||
|
||||
/// <summary>
|
||||
/// Gets renderers for the object.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Renderer> Renderers => _renderers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets outline settings for this object.
|
||||
/// </summary>
|
||||
public IOutlineSettings OutlineSettings => _outlineSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderObject"/> struct.
|
||||
/// </summary>
|
||||
public OutlineRenderObject(IReadOnlyList<Renderer> renderers, IOutlineSettings outlineSettings, string tag = null)
|
||||
{
|
||||
_renderers = renderers;
|
||||
_outlineSettings = outlineSettings;
|
||||
_tag = tag;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(OutlineRenderObject other)
|
||||
{
|
||||
return string.CompareOrdinal(_tag, other._tag) == 0 && _renderers == other._renderers && _outlineSettings == other._outlineSettings;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9fa0d37014ee9049afd5e65be9f288b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,476 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for outline rendering with <see cref="CommandBuffer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The class can be used on its own or as part of a higher level systems. It is used
|
||||
/// by higher level outline implementations (<see cref="OutlineEffect"/> and
|
||||
/// <see cref="OutlineBehaviour"/>). It is fully compatible with Unity post processing stack as well.</para>
|
||||
/// <para>The class implements <see cref="IDisposable"/> to be used inside <see langword="using"/>
|
||||
/// block as shown in the code samples. Disposing <see cref="OutlineRenderer"/> does not dispose
|
||||
/// the corresponding <see cref="CommandBuffer"/>.</para>
|
||||
/// <para>Command buffer is not cleared before rendering. It is user responsibility to do so if needed.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// var commandBuffer = new CommandBuffer();
|
||||
///
|
||||
/// using (var renderer = new OutlineRenderer(commandBuffer, resources))
|
||||
/// {
|
||||
/// renderer.Render(renderers, settings);
|
||||
/// }
|
||||
///
|
||||
/// camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
|
||||
/// </example>
|
||||
/// <seealso cref="OutlineResources"/>
|
||||
public readonly struct OutlineRenderer : IDisposable
|
||||
{
|
||||
#region data
|
||||
|
||||
private readonly TextureDimension _rtDimention;
|
||||
private readonly RenderTargetIdentifier _rt;
|
||||
private readonly RenderTargetIdentifier _depth;
|
||||
private readonly CommandBuffer _commandBuffer;
|
||||
private readonly OutlineResources _resources;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
/// <summary>
|
||||
/// A default <see cref="CameraEvent"/> outline rendering should be assosiated with.
|
||||
/// </summary>
|
||||
public const CameraEvent RenderEvent = CameraEvent.AfterSkybox;
|
||||
|
||||
/// <summary>
|
||||
/// A default render texture format for the outline effect.
|
||||
/// </summary>
|
||||
public const RenderTextureFormat RtFormat = RenderTextureFormat.R8;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="resources">Outline resources.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources)
|
||||
: this(cmd, resources, BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.Depth, Vector2Int.zero)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="resources">Outline resources.</param>
|
||||
/// <param name="renderingPath">The rendering path of target camera (<see cref="Camera.actualRenderingPath"/>).</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderingPath renderingPath)
|
||||
: this(cmd, resources, BuiltinRenderTextureType.CameraTarget, GetBuiltinDepth(renderingPath), Vector2Int.zero)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="resources">Outline resources.</param>
|
||||
/// <param name="dst">Render target.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst)
|
||||
: this(cmd, resources, dst, BuiltinRenderTextureType.Depth, Vector2Int.zero)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="dst">Render target.</param>
|
||||
/// <param name="renderingPath">The rendering path of target camera (<see cref="Camera.actualRenderingPath"/>).</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderingPath renderingPath, Vector2Int rtSize)
|
||||
: this(cmd, resources, dst, GetBuiltinDepth(renderingPath), rtSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="resources">Outline resources.</param>
|
||||
/// <param name="dst">Render target.</param>
|
||||
/// <param name="depth">Depth dexture to use.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderTargetIdentifier depth, Vector2Int rtSize)
|
||||
{
|
||||
if (cmd is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cmd));
|
||||
}
|
||||
|
||||
if (resources is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(resources));
|
||||
}
|
||||
|
||||
if (rtSize.x <= 0)
|
||||
{
|
||||
rtSize.x = -1;
|
||||
}
|
||||
|
||||
if (rtSize.y <= 0)
|
||||
{
|
||||
rtSize.y = -1;
|
||||
}
|
||||
|
||||
if (XRSettings.enabled)
|
||||
{
|
||||
var rtDesc = XRSettings.eyeTextureDesc;
|
||||
|
||||
rtDesc.shadowSamplingMode = ShadowSamplingMode.None;
|
||||
rtDesc.depthBufferBits = 0;
|
||||
rtDesc.colorFormat = RtFormat;
|
||||
|
||||
cmd.GetTemporaryRT(resources.MaskTexId, rtDesc, FilterMode.Bilinear);
|
||||
cmd.GetTemporaryRT(resources.TempTexId, rtDesc, FilterMode.Bilinear);
|
||||
|
||||
_rtDimention = rtDesc.dimension;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.GetTemporaryRT(resources.MaskTexId, rtSize.x, rtSize.y, 0, FilterMode.Bilinear, RtFormat);
|
||||
cmd.GetTemporaryRT(resources.TempTexId, rtSize.x, rtSize.y, 0, FilterMode.Bilinear, RtFormat);
|
||||
|
||||
_rtDimention = TextureDimension.Tex2D;
|
||||
}
|
||||
|
||||
_rt = dst;
|
||||
_depth = depth;
|
||||
_commandBuffer = cmd;
|
||||
_resources = resources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OutlineRenderer"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="cmd">A <see cref="CommandBuffer"/> to render the effect to. It should be cleared manually (if needed) before passing to this method.</param>
|
||||
/// <param name="resources">Outline resources.</param>
|
||||
/// <param name="dst">Render target.</param>
|
||||
/// <param name="depth">Depth dexture to use.</param>
|
||||
/// <param name="rtDesc">Render texture decsriptor.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cmd"/> is <see langword="null"/>.</exception>
|
||||
public OutlineRenderer(CommandBuffer cmd, OutlineResources resources, RenderTargetIdentifier dst, RenderTargetIdentifier depth, RenderTextureDescriptor rtDesc)
|
||||
{
|
||||
if (cmd is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cmd));
|
||||
}
|
||||
|
||||
if (resources is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(resources));
|
||||
}
|
||||
|
||||
if (rtDesc.width <= 0)
|
||||
{
|
||||
rtDesc.width = -1;
|
||||
}
|
||||
|
||||
if (rtDesc.height <= 0)
|
||||
{
|
||||
rtDesc.height = -1;
|
||||
}
|
||||
|
||||
if (rtDesc.dimension == TextureDimension.None || rtDesc.dimension == TextureDimension.Unknown)
|
||||
{
|
||||
rtDesc.dimension = TextureDimension.Tex2D;
|
||||
}
|
||||
|
||||
rtDesc.shadowSamplingMode = ShadowSamplingMode.None;
|
||||
rtDesc.depthBufferBits = 0;
|
||||
rtDesc.colorFormat = RtFormat;
|
||||
rtDesc.msaaSamples = 1;
|
||||
|
||||
cmd.GetTemporaryRT(resources.MaskTexId, rtDesc, FilterMode.Bilinear);
|
||||
cmd.GetTemporaryRT(resources.TempTexId, rtDesc, FilterMode.Bilinear);
|
||||
|
||||
_rtDimention = rtDesc.dimension;
|
||||
_rt = dst;
|
||||
_depth = depth;
|
||||
_commandBuffer = cmd;
|
||||
_resources = resources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders outline around a single object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object to be outlined.</param>
|
||||
/// <seealso cref="Render(IReadOnlyList{OutlineRenderObject})"/>
|
||||
public void Render(OutlineRenderObject obj)
|
||||
{
|
||||
Render(obj.Renderers, obj.OutlineSettings, obj.Tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders outline around multiple <paramref name="objects"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">An object to be outlined.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="objects"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="Render(OutlineRenderObject)"/>
|
||||
public void Render(IReadOnlyList<OutlineRenderObject> objects)
|
||||
{
|
||||
if (objects is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objects));
|
||||
}
|
||||
|
||||
for (var i = 0; i < objects.Count; i++)
|
||||
{
|
||||
Render(objects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders outline around multiple <paramref name="renderers"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderers">One or more renderers representing a single object to be outlined.</param>
|
||||
/// <param name="settings">Outline settings.</param>
|
||||
/// <param name="sampleName">Optional name of the sample (visible in profiler).</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if any of the arguments is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="Render(Renderer, IOutlineSettings, string)"/>
|
||||
public void Render(IReadOnlyList<Renderer> renderers, IOutlineSettings settings, string sampleName = null)
|
||||
{
|
||||
if (renderers is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderers));
|
||||
}
|
||||
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
if (renderers.Count > 0)
|
||||
{
|
||||
// NOTE: Remove BeginSample/EndSample for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44).
|
||||
//if (string.IsNullOrEmpty(sampleName))
|
||||
//{
|
||||
// sampleName = renderers[0].name;
|
||||
//}
|
||||
|
||||
//_commandBuffer.BeginSample(sampleName);
|
||||
{
|
||||
RenderObjectClear(settings.OutlineRenderMode);
|
||||
|
||||
for (var i = 0; i < renderers.Count; ++i)
|
||||
{
|
||||
DrawRenderer(renderers[i], settings);
|
||||
}
|
||||
|
||||
RenderOutline(settings);
|
||||
}
|
||||
//_commandBuffer.EndSample(sampleName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders outline around a single <paramref name="renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">A <see cref="Renderer"/> representing an object to be outlined.</param>
|
||||
/// <param name="settings">Outline settings.</param>
|
||||
/// <param name="sampleName">Optional name of the sample (visible in profiler).</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if any of the arguments is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="Render(IReadOnlyList{Renderer}, IOutlineSettings, string)"/>
|
||||
public void Render(Renderer renderer, IOutlineSettings settings, string sampleName = null)
|
||||
{
|
||||
if (renderer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
// NOTE: Remove BeginSample/EndSample for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44).
|
||||
//if (string.IsNullOrEmpty(sampleName))
|
||||
//{
|
||||
// sampleName = renderer.name;
|
||||
//}
|
||||
|
||||
// NOTE: Remove this for now (https://github.com/Arvtesh/UnityFx.Outline/issues/44).
|
||||
//_commandBuffer.BeginSample(sampleName);
|
||||
{
|
||||
RenderObjectClear(settings.OutlineRenderMode);
|
||||
DrawRenderer(renderer, settings);
|
||||
RenderOutline(settings);
|
||||
}
|
||||
//_commandBuffer.EndSample(sampleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specialized render target setup. Do not use if not sure.
|
||||
/// </summary>
|
||||
public void RenderObjectClear(OutlineRenderFlags flags)
|
||||
{
|
||||
// NOTE: Use the camera depth buffer when rendering the mask. Shader only reads from the depth buffer (ZWrite Off).
|
||||
if ((flags & OutlineRenderFlags.EnableDepthTesting) != 0)
|
||||
{
|
||||
if (_rtDimention == TextureDimension.Tex2DArray)
|
||||
{
|
||||
// NOTE: Need to use this SetRenderTarget overload for XR, otherwise single pass instanced rendering does not function properly.
|
||||
_commandBuffer.SetRenderTarget(_resources.MaskTex, _depth, 0, CubemapFace.Unknown, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer.SetRenderTarget(_resources.MaskTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, _depth, RenderBufferLoadAction.Load, RenderBufferStoreAction.DontCare);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_rtDimention == TextureDimension.Tex2DArray)
|
||||
{
|
||||
_commandBuffer.SetRenderTarget(_resources.MaskTex, 0, CubemapFace.Unknown, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer.SetRenderTarget(_resources.MaskTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
||||
}
|
||||
}
|
||||
|
||||
_commandBuffer.ClearRenderTarget(false, true, Color.clear);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders outline. Do not use if not sure.
|
||||
/// </summary>
|
||||
public void RenderOutline(IOutlineSettings settings)
|
||||
{
|
||||
var mat = _resources.OutlineMaterial;
|
||||
var props = _resources.GetProperties(settings);
|
||||
|
||||
_commandBuffer.SetGlobalFloatArray(_resources.GaussSamplesId, _resources.GetGaussSamples(settings.OutlineWidth));
|
||||
|
||||
if (_rtDimention == TextureDimension.Tex2DArray)
|
||||
{
|
||||
// HPass
|
||||
_commandBuffer.SetRenderTarget(_resources.TempTex, 0, CubemapFace.Unknown, -1);
|
||||
Blit(_resources.MaskTex, OutlineResources.OutlineShaderHPassId, mat, props);
|
||||
|
||||
// VPassBlend
|
||||
_commandBuffer.SetRenderTarget(_rt, 0, CubemapFace.Unknown, -1);
|
||||
Blit(_resources.TempTex, OutlineResources.OutlineShaderVPassId, mat, props);
|
||||
}
|
||||
else
|
||||
{
|
||||
// HPass
|
||||
_commandBuffer.SetRenderTarget(_resources.TempTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
||||
Blit(_resources.MaskTex, OutlineResources.OutlineShaderHPassId, mat, props);
|
||||
|
||||
// VPassBlend
|
||||
_commandBuffer.SetRenderTarget(_rt, RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
|
||||
Blit(_resources.TempTex, OutlineResources.OutlineShaderVPassId, mat, props);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the effect rendering and releases temporary textures used. Should only be called once.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_commandBuffer.ReleaseTemporaryRT(_resources.TempTexId);
|
||||
_commandBuffer.ReleaseTemporaryRT(_resources.MaskTexId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
|
||||
private void DrawRenderer(Renderer renderer, IOutlineSettings settings)
|
||||
{
|
||||
if (renderer && renderer.enabled && renderer.isVisible && renderer.gameObject.activeInHierarchy)
|
||||
{
|
||||
// NOTE: Accessing Renderer.sharedMaterials triggers GC.Alloc. That's why we use a temporary
|
||||
// list of materials, cached with the outline resources.
|
||||
renderer.GetSharedMaterials(_resources.TmpMaterials);
|
||||
|
||||
if (_resources.TmpMaterials.Count > 0)
|
||||
{
|
||||
if (settings.IsAlphaTestingEnabled())
|
||||
{
|
||||
for (var i = 0; i < _resources.TmpMaterials.Count; ++i)
|
||||
{
|
||||
var mat = _resources.TmpMaterials[i];
|
||||
|
||||
// Use material cutoff value if available.
|
||||
if (mat.HasProperty(_resources.AlphaCutoffId))
|
||||
{
|
||||
_commandBuffer.SetGlobalFloat(_resources.AlphaCutoffId, mat.GetFloat(_resources.AlphaCutoffId));
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer.SetGlobalFloat(_resources.AlphaCutoffId, settings.OutlineAlphaCutoff);
|
||||
}
|
||||
|
||||
_commandBuffer.SetGlobalTexture(_resources.MainTexId, _resources.TmpMaterials[i].mainTexture);
|
||||
_commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, i, OutlineResources.RenderShaderAlphaTestPassId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < _resources.TmpMaterials.Count; ++i)
|
||||
{
|
||||
_commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, i, OutlineResources.RenderShaderDefaultPassId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: No materials set for renderer means we should still render outline for it.
|
||||
_commandBuffer.DrawRenderer(renderer, _resources.RenderMaterial, 0, OutlineResources.RenderShaderDefaultPassId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Blit(RenderTargetIdentifier src, int shaderPass, Material mat, MaterialPropertyBlock props)
|
||||
{
|
||||
// Set source texture as _MainTex to match Blit behavior.
|
||||
_commandBuffer.SetGlobalTexture(_resources.MainTexId, src);
|
||||
|
||||
// NOTE: SystemInfo.graphicsShaderLevel check is not enough sometimes (esp. on mobiles), so there is SystemInfo.supportsInstancing
|
||||
// check and a flag for forcing DrawMesh.
|
||||
if (SystemInfo.graphicsShaderLevel >= 35 && SystemInfo.supportsInstancing && !_resources.UseFullscreenTriangleMesh)
|
||||
{
|
||||
_commandBuffer.DrawProcedural(Matrix4x4.identity, mat, shaderPass, MeshTopology.Triangles, 3, 1, props);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandBuffer.DrawMesh(_resources.FullscreenTriangleMesh, Matrix4x4.identity, mat, 0, shaderPass, props);
|
||||
}
|
||||
}
|
||||
|
||||
private static RenderTargetIdentifier GetBuiltinDepth(RenderingPath renderingPath)
|
||||
{
|
||||
return (renderingPath == RenderingPath.DeferredShading || renderingPath == RenderingPath.DeferredLighting) ? BuiltinRenderTextureType.ResolvedDepth : BuiltinRenderTextureType.Depth;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4271470bd9f5d5041a4a8881d8457a55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
|
||||
// See the LICENSE.md file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityFx.Outline
|
||||
{
|
||||
internal class OutlineRendererCollection : ICollection<Renderer>
|
||||
{
|
||||
#region data
|
||||
|
||||
private readonly List<Renderer> _renderers = new List<Renderer>();
|
||||
private readonly GameObject _go;
|
||||
|
||||
#endregion
|
||||
|
||||
#region interface
|
||||
|
||||
internal OutlineRendererCollection(GameObject go)
|
||||
{
|
||||
Debug.Assert(go);
|
||||
_go = go;
|
||||
}
|
||||
|
||||
internal IReadOnlyList<Renderer> GetList()
|
||||
{
|
||||
return _renderers;
|
||||
}
|
||||
|
||||
internal void Reset(bool includeInactive)
|
||||
{
|
||||
_go.GetComponentsInChildren(includeInactive, _renderers);
|
||||
}
|
||||
|
||||
internal void Reset(bool includeInactive, int ignoreLayerMask)
|
||||
{
|
||||
_renderers.Clear();
|
||||
|
||||
if (ignoreLayerMask != 0)
|
||||
{
|
||||
var renderers = _go.GetComponentsInChildren<Renderer>(includeInactive);
|
||||
|
||||
foreach (var renderer in renderers)
|
||||
{
|
||||
if (((1 << renderer.gameObject.layer) & ignoreLayerMask) == 0)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_go.GetComponentsInChildren(includeInactive, _renderers);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection
|
||||
|
||||
public int Count => _renderers.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(Renderer renderer)
|
||||
{
|
||||
Validate(renderer);
|
||||
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
public bool Remove(Renderer renderer)
|
||||
{
|
||||
return _renderers.Remove(renderer);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_renderers.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(Renderer renderer)
|
||||
{
|
||||
return _renderers.Contains(renderer);
|
||||
}
|
||||
|
||||
public void CopyTo(Renderer[] array, int arrayIndex)
|
||||
{
|
||||
_renderers.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable
|
||||
|
||||
public IEnumerator<Renderer> GetEnumerator()
|
||||
{
|
||||
return _renderers.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _renderers.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region implementation
|
||||
|
||||
private void Validate(Renderer renderer)
|
||||
{
|
||||
if (renderer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
if (!renderer.transform.IsChildOf(_go.transform))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Only children of the {0} are allowed.", _go.name), nameof(renderer));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89621a3cc73c4e6498a00b2d180ed462
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user