This commit is contained in:
2025-07-20 21:01:09 +08:00
commit d2b8ce01f8
110 changed files with 6418 additions and 0 deletions
+51
View File
@@ -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; }
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: efc18f75d5206f14a80e9306650c858a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+443
View File
@@ -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
}
}
+15
View File
@@ -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:
+93
View File
@@ -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
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e746e776b0ae00d4a9d458b9430b95d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+267
View File
@@ -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
}
}
+15
View File
@@ -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:
+15
View File
@@ -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,
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82c9d42cc303be24d852b8db7c4b650f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+509
View File
@@ -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
}
}
+13
View File
@@ -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:
+320
View File
@@ -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:
+532
View File
@@ -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
}
}
+13
View File
@@ -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:
+144
View File
@@ -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
}
}
+11
View File
@@ -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:
+139
View File
@@ -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:
+8
View File
@@ -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:
+10
View File
@@ -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:
+8
View File
@@ -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: