diff --git a/Assets/Examples/Prefabs/TestOutlineLayers.asset b/Assets/Examples/Prefabs/TestOutlineLayers.asset index 4db7174..47339ac 100644 --- a/Assets/Examples/Prefabs/TestOutlineLayers.asset +++ b/Assets/Examples/Prefabs/TestOutlineLayers.asset @@ -19,6 +19,8 @@ MonoBehaviour: _outlineWidth: 5 _outlineIntensity: 2 _outlineMode: 0 + _name: + _zOrder: 0 _enabled: 1 - _settings: _outlineSettings: {fileID: 0} @@ -26,6 +28,8 @@ MonoBehaviour: _outlineWidth: 15 _outlineIntensity: 2 _outlineMode: 1 + _name: + _zOrder: 0 _enabled: 1 - _settings: _outlineSettings: {fileID: 0} @@ -33,4 +37,6 @@ MonoBehaviour: _outlineWidth: 4 _outlineIntensity: 2 _outlineMode: 0 + _name: + _zOrder: 0 _enabled: 1 diff --git a/Assets/Examples/Scripts/OutlineEffectBuilder.cs b/Assets/Examples/Scripts/OutlineEffectBuilder.cs index ff2e31b..c5a2355 100644 --- a/Assets/Examples/Scripts/OutlineEffectBuilder.cs +++ b/Assets/Examples/Scripts/OutlineEffectBuilder.cs @@ -47,6 +47,14 @@ private void Awake() _outlineEffect.OutlineLayers.Add(_outlineLayer); } } + + foreach (var go in _outlineGos) + { + if (go) + { + _outlineLayer.Add(go); + } + } } private void OnValidate() diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/IOutlineSettings.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/IOutlineSettings.cs index bf49e44..cc46038 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/IOutlineSettings.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/IOutlineSettings.cs @@ -9,7 +9,7 @@ namespace UnityFx.Outline /// /// Defines outline settings. /// - public interface IOutlineSettings + public interface IOutlineSettings : IEquatable { /// /// Gets or sets outline color. diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs index 4f41843..11e3e0c 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs @@ -171,9 +171,11 @@ private void Update() if (_outlineResources != null && _renderers != null && (_outlineSettings.IsChanged || _commandBuffer.sizeInBytes == 0)) { + _commandBuffer.Clear(); + using (var renderer = new OutlineRenderer(_commandBuffer, BuiltinRenderTextureType.CameraTarget)) { - renderer.RenderSingleObject(_renderers, _outlineSettings.OutlineMaterials); + renderer.Render(_renderers, _outlineSettings.OutlineResources, _outlineSettings); } _outlineSettings.AcceptChanges(); @@ -297,6 +299,16 @@ public OutlineMode OutlineMode #endregion + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return OutlineSettings.Equals(_outlineSettings, other); + } + + #endregion + #region implementation private void RemoveDestroyedCameras() diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs index c5f9e75..fe306cd 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs @@ -264,6 +264,8 @@ private void Reset() private void FillCommandBuffer() { + _commandBuffer.Clear(); + if (_outlineResources && _outlineResources.IsValid) { using (var renderer = new OutlineRenderer(_commandBuffer, BuiltinRenderTextureType.CameraTarget)) @@ -271,10 +273,6 @@ private void FillCommandBuffer() _outlineLayers.Render(renderer, _outlineResources); } } - else - { - _commandBuffer.Clear(); - } _changed = false; diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs index acbcf47..f909b66 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs @@ -210,6 +210,25 @@ public bool TryGetRenderers(GameObject go, out ICollection renderers) return false; } + /// + /// Renders the layers. + /// + public void Render(OutlineRenderer renderer, OutlineResources resources) + { + if (_enabled) + { + _settings.SetResources(resources); + + foreach (var kvp in _outlineObjects) + { + if (kvp.Key && kvp.Key.activeInHierarchy) + { + renderer.Render(kvp.Value, _settings.OutlineResources, _settings); + } + } + } + } + #endregion #region internals @@ -257,22 +276,6 @@ internal void SetCollection(OutlineLayerCollection collection) } } - internal void Render(OutlineRenderer renderer, OutlineResources resources) - { - if (_enabled) - { - _settings.SetResources(resources); - - foreach (var kvp in _outlineObjects) - { - if (kvp.Key && kvp.Key.activeInHierarchy) - { - renderer.RenderSingleObject(kvp.Value, _settings.OutlineMaterials); - } - } - } - } - #endregion #region IOutlineSettingsEx @@ -452,8 +455,19 @@ public void AcceptChanges() #endregion + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return OutlineSettings.Equals(this, other); + } + + #endregion + #region Object + /// public override string ToString() { var text = new StringBuilder(); @@ -496,6 +510,18 @@ public override string ToString() return string.Format("{0}", text); } + /// + public override bool Equals(object other) + { + return OutlineSettings.Equals(this, other as IOutlineSettings); + } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + #endregion #region implementation diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs index 77b8a65..9ec46cf 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs @@ -47,11 +47,24 @@ public OutlineLayer[] SortedLayers { get { - UpdateSortedLayersIdNeeded(); + UpdateSortedLayersIfNeeded(); return _sortedLayers.ToArray(); } } + /// + /// Renders all layers. + /// + public void Render(OutlineRenderer renderer, OutlineResources resources) + { + UpdateSortedLayersIfNeeded(); + + foreach (var layer in _sortedLayers) + { + layer.Render(renderer, resources); + } + } + #endregion #region internals @@ -78,16 +91,6 @@ internal void UpdateChanged() } } - internal void Render(OutlineRenderer renderer, OutlineResources resources) - { - UpdateSortedLayersIdNeeded(); - - foreach (var layer in _sortedLayers) - { - layer.Render(renderer, resources); - } - } - #endregion #region ScriptableObject @@ -327,7 +330,7 @@ public void AcceptChanges() #region implementation - private void UpdateSortedLayersIdNeeded() + private void UpdateSortedLayersIfNeeded() { if (_orderChanged) { diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs deleted file mode 100644 index e3e8ce7..0000000 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (C) 2019 Alexander Bogarsukov. All rights reserved. -// See the LICENSE.md file in the project root for more information. - -using System; -using UnityEngine; - -namespace UnityFx.Outline -{ - /// - /// A set of materials needed to render outlines. - /// - /// - public sealed class OutlineMaterialSet : IOutlineSettings, IDisposable - { - #region data - - private readonly OutlineResources _outlineResources; - private readonly Material _renderMaterial; - private readonly Material _hPassMaterial; - private readonly Material _vPassMaterial; - private readonly float[] _gaussSamples = new float[OutlineRenderer.MaxWidth]; - - private Color _color; - private int _width = OutlineRenderer.MinWidth; - private float _intensity = OutlineRenderer.MinIntensity; - private OutlineMode _mode; - private bool _disposed; - - #endregion - - #region interface - - /// - /// NameID of the outline color shader parameter. - /// - public readonly int ColorNameId = Shader.PropertyToID("_Color"); - - /// - /// NameID of the outline width shader parameter. - /// - public readonly int WidthNameId = Shader.PropertyToID("_Width"); - - /// - /// NameID of the outline intensity shader parameter. - /// - public readonly int IntensityNameId = Shader.PropertyToID("_Intensity"); - - /// - /// NameID of the outline width shader parameter. - /// - public readonly int GaussSamplesNameId = Shader.PropertyToID("_GaussSamples"); - - /// - /// Gets resources used by the effect implementation. - /// - public OutlineResources OutlineResources - { - get - { - return _outlineResources; - } - } - - /// - /// Gets material for . - /// - /// - /// - public Material RenderMaterial - { - get - { - return _renderMaterial; - } - } - - /// - /// Gets material for . - /// - /// - /// - public Material HPassMaterial - { - get - { - return _hPassMaterial; - } - } - - /// - /// Gets material for . - /// - /// - /// - public Material VPassBlendMaterial - { - get - { - return _vPassMaterial; - } - } - - /// - /// Gets Gauss samples for blur calculations. - /// - public float[] GaussSamples - { - get - { - return _gaussSamples; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The preferred way of creating instances of is calling method. - /// - public OutlineMaterialSet(OutlineResources resources) - { - if (resources == null) - { - throw new ArgumentNullException("resources"); - } - - _outlineResources = resources; - _renderMaterial = new Material(resources.RenderShader); - _hPassMaterial = new Material(resources.HPassShader); - _vPassMaterial = new Material(resources.VPassBlendShader); - - _hPassMaterial.SetInt(WidthNameId, _width); - _vPassMaterial.SetInt(WidthNameId, _width); - _vPassMaterial.SetColor(ColorNameId, _color); - _vPassMaterial.SetFloat(IntensityNameId, OutlineRenderer.SolidIntensity); - } - - /// - /// Initializes a new instance of the class. - /// - internal OutlineMaterialSet(OutlineResources resources, Material renderMaterial) - { - Debug.Assert(resources); - Debug.Assert(resources.IsValid); - Debug.Assert(renderMaterial); - - _outlineResources = resources; - _renderMaterial = renderMaterial; - _hPassMaterial = new Material(resources.HPassShader); - _vPassMaterial = new Material(resources.VPassBlendShader); - - _hPassMaterial.SetInt(WidthNameId, _width); - _vPassMaterial.SetInt(WidthNameId, _width); - _vPassMaterial.SetColor(ColorNameId, _color); - _vPassMaterial.SetFloat(IntensityNameId, OutlineRenderer.SolidIntensity); - } - - /// - /// Resets materials state. - /// - /// - /// - /// - public void Reset(IOutlineSettings settings) - { - ThrowIfDisposed(); - - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - SetColor(settings.OutlineColor); - SetIntensity(settings.OutlineIntensity); - SetWidth(settings.OutlineWidth); - SetMode(settings.OutlineMode); - UpdateGaussSamples(); - } - - #endregion - - #region IOutlineSettings - - /// - public Color OutlineColor - { - get - { - return _color; - } - set - { - ThrowIfDisposed(); - SetColor(value); - } - } - - /// - public int OutlineWidth - { - get - { - return _width; - } - set - { - ThrowIfDisposed(); - - if (_width != value) - { - SetWidth(value); - UpdateGaussSamples(); - } - } - } - - /// - public float OutlineIntensity - { - get - { - return _intensity; - } - set - { - ThrowIfDisposed(); - - if (_intensity != value) - { - SetIntensity(value); - } - } - } - - /// - public OutlineMode OutlineMode - { - get - { - return _mode; - } - set - { - ThrowIfDisposed(); - - if (_mode != value) - { - SetMode(value); - } - } - } - - #endregion - - #region IDisposable - - /// - public void Dispose() - { - if (!_disposed) - { - _disposed = true; - - if (_renderMaterial) - { - UnityEngine.Object.DestroyImmediate(_renderMaterial); - } - - if (_hPassMaterial) - { - UnityEngine.Object.DestroyImmediate(_hPassMaterial); - } - - if (_vPassMaterial) - { - UnityEngine.Object.DestroyImmediate(_vPassMaterial); - } - } - } - - #endregion - - #region implementation - - private void SetColor(Color color) - { - _color = color; - _vPassMaterial.SetColor(ColorNameId, color); - } - - private void SetWidth(int width) - { - _width = Mathf.Clamp(width, OutlineRenderer.MinWidth, OutlineRenderer.MaxWidth); - _hPassMaterial.SetInt(WidthNameId, _width); - _vPassMaterial.SetInt(WidthNameId, _width); - } - - private void SetIntensity(float intensity) - { - _intensity = Mathf.Clamp(intensity, OutlineRenderer.MinIntensity, OutlineRenderer.MaxIntensity); - _vPassMaterial.SetFloat(IntensityNameId, _intensity); - } - - private void SetMode(OutlineMode mode) - { - _mode = mode; - - if (mode == OutlineMode.Solid) - { - _vPassMaterial.SetFloat(IntensityNameId, OutlineRenderer.SolidIntensity); - } - else - { - _vPassMaterial.SetFloat(IntensityNameId, _intensity); - } - } - - private void UpdateGaussSamples() - { - OutlineRenderer.GetGaussSamples(_width, _gaussSamples); - } - - private void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - #endregion - } -} diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs.meta b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs.meta deleted file mode 100644 index 60b81e5..0000000 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineMaterialSet.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c4199c3138c5f4d45ac4e1663e30ee7d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs index 0866684..aa08159 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs @@ -9,37 +9,74 @@ namespace UnityFx.Outline { /// - /// Helper low-level class for building outline . + /// Helper class for outline rendering with . /// /// - /// This class is used by higher level outline implementations ( and ). - /// It implements to be used inside block as shown in the code sample. Disposing - /// does not dispose the . + /// The class can be used on its own or as part of a higher level systems. It is used + /// by higher level outline implementations ( and + /// ). It is fully compatible with Unity post processing stack as well. + /// The class implements to be used inside + /// block as shown in the code samples. Disposing does not dispose + /// the corresponding . + /// Command buffer is not cleared before rendering. It is user responsibility to do so if needed. /// /// + /// var commandBuffer = new CommandBuffer(); + /// /// using (var renderer = new OutlineRenderer(commandBuffer, BuiltinRenderTextureType.CameraTarget)) /// { - /// renderer.RenderSingleObject(outlineRenderers, renderMaterial, postProcessMaterial); + /// renderer.Render(renderers, resources, settings); /// } + /// + /// camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer); /// - /// + /// + /// [Preserve] + /// public class OutlineEffectRenderer : PostProcessEffectRenderer + /// { + /// public override void Init() + /// { + /// base.Init(); + /// + /// // Reuse fullscreen triangle mesh from PostProcessing (do not create own). + /// settings.OutlineResources.FullscreenTriangleMesh = RuntimeUtilities.fullscreenTriangle; + /// } + /// + /// public override void Render(PostProcessRenderContext context) + /// { + /// var resources = settings.OutlineResources; + /// var layers = settings.OutlineLayers; + /// + /// if (resources && resources.IsValid && layers) + /// { + /// // No need to setup property sheet parameters, all the rendering staff is handled by the OutlineRenderer. + /// using (var renderer = new OutlineRenderer(context.command, context.source, context.destination)) + /// { + /// layers.Render(renderer, resources); + /// } + /// } + /// } + /// } + /// + /// public struct OutlineRenderer : IDisposable { #region data - private readonly int _maskRtId; - private readonly int _hPassRtId; - private readonly RenderTargetIdentifier _renderTarget; - private readonly CommandBuffer _commandBuffer; + private static readonly int _mainRtId = Shader.PropertyToID("_MainTex"); + private static readonly int _maskRtId = Shader.PropertyToID("_MaskTex"); + private static readonly int _hPassRtId = Shader.PropertyToID("_HPassTex"); - private bool _disposed; + private readonly RenderTargetIdentifier _source; + private readonly RenderTargetIdentifier _destination; + private readonly CommandBuffer _commandBuffer; #endregion #region interface /// - /// A outline rendering should be assosiated with. + /// A default outline rendering should be assosiated with. /// public const CameraEvent RenderEvent = CameraEvent.BeforeImageEffects; @@ -51,120 +88,158 @@ public struct OutlineRenderer : IDisposable /// /// Minimum value of outline width parameter. /// + /// public const int MinWidth = 1; /// /// Maximum value of outline width parameter. /// + /// public const int MaxWidth = 32; /// /// Minimum value of outline intensity parameter. /// + /// + /// public const int MinIntensity = 1; /// /// Maximum value of outline intensity parameter. /// + /// + /// public const int MaxIntensity = 64; /// /// Value of outline intensity parameter that is treated as solid fill. /// + /// + /// public const int SolidIntensity = 100; /// /// Initializes a new instance of the struct. /// - public OutlineRenderer(CommandBuffer commandBuffer, BuiltinRenderTextureType dst) - : this(commandBuffer, new RenderTargetIdentifier(dst)) + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Render target. + /// Thrown if is . + public OutlineRenderer(CommandBuffer commandBuffer, BuiltinRenderTextureType rt) + : this(commandBuffer, rt, rt) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Render target. + /// Thrown if is . + public OutlineRenderer(CommandBuffer commandBuffer, RenderTargetIdentifier rt) + : this(commandBuffer, rt, rt) { } /// /// Initializes a new instance of the struct. /// - public OutlineRenderer(CommandBuffer commandBuffer, RenderTargetIdentifier dst) + /// A to render the effect to. It should be cleared manually (if needed) before passing to this method. + /// Source image. Can be the same as . + /// Render target. + /// Thrown if is . + public OutlineRenderer(CommandBuffer commandBuffer, RenderTargetIdentifier src, RenderTargetIdentifier dst) { - Debug.Assert(commandBuffer != null); + if (commandBuffer == null) + { + throw new ArgumentNullException("commandBuffer"); + } - _disposed = false; - _maskRtId = Shader.PropertyToID("_MaskTex"); - _hPassRtId = Shader.PropertyToID("_HPassTex"); - _renderTarget = dst; + _source = src; + _destination = dst; _commandBuffer = commandBuffer; - _commandBuffer.Clear(); _commandBuffer.BeginSample(EffectName); _commandBuffer.GetTemporaryRT(_maskRtId, -1, -1, 0, FilterMode.Bilinear, RenderTextureFormat.R8); _commandBuffer.GetTemporaryRT(_hPassRtId, -1, -1, 0, FilterMode.Bilinear, RenderTextureFormat.R8); + + // Need to copy src content into dst if they are not the same. For instance this is the case when rendering + // the outline effect as part of Unity Post Processing stack. + if (!src.Equals(dst)) + { + if (SystemInfo.copyTextureSupport > CopyTextureSupport.None) + { + _commandBuffer.CopyTexture(src, dst); + } + else + { +#if UNITY_2018_2_OR_NEWER + _commandBuffer.SetRenderTarget(dst, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); +#else + _commandBuffer.SetRenderTarget(dst); +#endif + _commandBuffer.Blit(src, BuiltinRenderTextureType.CurrentActive); + } + } } /// - /// Adds commands for rendering single outline object. + /// Renders outline around a single object. /// - public void RenderSingleObject(IEnumerable renderers, OutlineMaterialSet materials) + /// One or more renderers representing a single object to be outlined. + /// Outline resources. + /// Outline settings. + /// Thrown if any of the arguments is . + public void Render(IEnumerable renderers, OutlineResources resources, IOutlineSettings settings) { if (renderers == null) { throw new ArgumentNullException("renderers"); } - if (materials == null) + if (resources == null) { - throw new ArgumentNullException("materials"); + throw new ArgumentNullException("resources"); } - _commandBuffer.SetRenderTarget(_maskRtId); - _commandBuffer.ClearRenderTarget(false, true, Color.black); - - foreach (var r in renderers) + if (settings == null) { - if (r && r.enabled && r.gameObject.activeInHierarchy) - { - for (var j = 0; j < r.sharedMaterials.Length; ++j) - { - _commandBuffer.DrawRenderer(r, materials.RenderMaterial, j); - } - } + throw new ArgumentNullException("settings"); } - _commandBuffer.SetGlobalFloatArray(materials.GaussSamplesNameId, materials.GaussSamples); - _commandBuffer.SetGlobalTexture(_maskRtId, _maskRtId); - _commandBuffer.Blit(_maskRtId, _hPassRtId, materials.HPassMaterial); - _commandBuffer.Blit(_hPassRtId, _renderTarget, materials.VPassBlendMaterial); + Init(resources, settings); + RenderObject(renderers, resources.RenderMaterial); + RenderHPass(resources, settings); + RenderVPassBlend(resources, settings); } /// - /// Adds commands for rendering single outline object. + /// Renders outline around a single object. /// - public void RenderSingleObject(Renderer renderer, OutlineMaterialSet materials) + /// A representing an object to be outlined. + /// Outline resources. + /// Outline settings. + /// Thrown if any of the arguments is . + public void Render(Renderer renderer, OutlineResources resources, IOutlineSettings settings) { if (renderer == null) { - throw new ArgumentNullException("renderer"); + throw new ArgumentNullException("renderers"); } - if (materials == null) + if (resources == null) { - throw new ArgumentNullException("materials"); + throw new ArgumentNullException("resources"); } - _commandBuffer.SetRenderTarget(_maskRtId); - _commandBuffer.ClearRenderTarget(false, true, Color.black); - - if (renderer && renderer.gameObject.activeInHierarchy && renderer.enabled) + if (settings == null) { - for (var i = 0; i < renderer.sharedMaterials.Length; ++i) - { - _commandBuffer.DrawRenderer(renderer, materials.RenderMaterial, i); - } + throw new ArgumentNullException("settings"); } - _commandBuffer.SetGlobalFloatArray(materials.GaussSamplesNameId, materials.GaussSamples); - _commandBuffer.SetGlobalTexture(_maskRtId, _maskRtId); - _commandBuffer.Blit(_maskRtId, _hPassRtId, materials.HPassMaterial); - _commandBuffer.Blit(_hPassRtId, _renderTarget, materials.VPassBlendMaterial); + Init(resources, settings); + RenderObject(renderer, resources.RenderMaterial); + RenderHPass(resources, settings); + RenderVPassBlend(resources, settings); } /// @@ -208,21 +283,115 @@ public static float[] GetGaussSamples(int width, float[] samples) #region IDisposable - /// + /// + /// Finalizes the effect rendering and releases temporary textures used. Should only be called once. + /// public void Dispose() { - if (!_disposed) - { - _disposed = true; - _commandBuffer.ReleaseTemporaryRT(_hPassRtId); - _commandBuffer.ReleaseTemporaryRT(_maskRtId); - _commandBuffer.EndSample(EffectName); - } + _commandBuffer.ReleaseTemporaryRT(_hPassRtId); + _commandBuffer.ReleaseTemporaryRT(_maskRtId); + _commandBuffer.EndSample(EffectName); } #endregion #region implementation + + private void Init(OutlineResources resources, IOutlineSettings settings) + { + _commandBuffer.SetGlobalFloatArray(resources.GaussSamplesId, resources.GetGaussSamples(settings.OutlineWidth)); + } + + private void RenderObject(IEnumerable renderers, Material mat) + { +#if UNITY_2018_2_OR_NEWER + _commandBuffer.SetRenderTarget(_maskRtId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); +#else + _commandBuffer.SetRenderTarget(_maskRtId); +#endif + _commandBuffer.ClearRenderTarget(false, true, Color.clear); + + foreach (var r in renderers) + { + if (r && r.enabled && r.gameObject.activeInHierarchy) + { + for (var j = 0; j < r.sharedMaterials.Length; ++j) + { + _commandBuffer.DrawRenderer(r, mat, j); + } + } + } + } + + private void RenderObject(Renderer renderer, Material mat) + { +#if UNITY_2018_2_OR_NEWER + _commandBuffer.SetRenderTarget(_maskRtId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); +#else + _commandBuffer.SetRenderTarget(_maskRtId); +#endif + _commandBuffer.ClearRenderTarget(false, true, Color.clear); + + if (renderer && renderer.gameObject.activeInHierarchy && renderer.enabled) + { + for (var i = 0; i < renderer.sharedMaterials.Length; ++i) + { + _commandBuffer.DrawRenderer(renderer, mat, i); + } + } + } + + private void RenderHPass(OutlineResources resources, IOutlineSettings settings) + { + // Setup shader parameter overrides. + var props = resources.HPassProperties; + props.SetFloat(resources.WidthId, settings.OutlineWidth); + + // Set source texture as _MainTex to match Blit behavior. + _commandBuffer.SetGlobalTexture(_mainRtId, _maskRtId); + + // Set destination texture as render target. +#if UNITY_2018_2_OR_NEWER + _commandBuffer.SetRenderTarget(_hPassRtId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); +#else + _commandBuffer.SetRenderTarget(_hPassRtId); +#endif + + // Blit fullscreen triangle. + _commandBuffer.DrawMesh(resources.FullscreenTriangleMesh, Matrix4x4.identity, resources.HPassMaterial, 0, 0, props); + } + + private void RenderVPassBlend(OutlineResources resources, IOutlineSettings settings) + { + // Setup shader parameter overrides. + var props = resources.VPassBlendProperties; + + props.SetFloat(resources.WidthId, settings.OutlineWidth); + props.SetColor(resources.ColorId, settings.OutlineColor); + + if (settings.OutlineMode == OutlineMode.Solid) + { + props.SetFloat(resources.IntensityId, SolidIntensity); + } + else + { + props.SetFloat(resources.IntensityId, settings.OutlineIntensity); + } + + // Set source texture as _MainTex to match Blit behavior. + _commandBuffer.SetGlobalTexture(_mainRtId, _source); + + // Set destination texture as render target. +#if UNITY_2018_2_OR_NEWER + _commandBuffer.SetRenderTarget(_destination, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); +#else + _commandBuffer.SetRenderTarget(_destination); +#endif + + // Blit fullscreen triangle. + _commandBuffer.DrawMesh(resources.FullscreenTriangleMesh, Matrix4x4.identity, resources.VPassBlendMaterial, 0, 0, props); + } + #endregion } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineResources.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineResources.cs index 98cbab0..36d4514 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineResources.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineResources.cs @@ -9,10 +9,43 @@ namespace UnityFx.Outline /// /// This asset is used to store references to shaders and other resources needed at runtime without having to use a Resources folder. /// + /// [CreateAssetMenu(fileName = "OutlineResources", menuName = "UnityFx/Outline/Outline Resources")] - public class OutlineResources : ScriptableObject + public sealed class OutlineResources : ScriptableObject { + #region data + private Material _renderMaterial; + private Material _hPassMaterial; + private Material _vPassMaterial; + private MaterialPropertyBlock _hPassProperties; + private MaterialPropertyBlock _vPassProperties; + private Mesh _fullscreenTriangleMesh; + private float[][] _gaussSmples; + + #endregion + + #region interface + + /// + /// Hashed name of _Color shader parameter. + /// + public readonly int ColorId = Shader.PropertyToID("_Color"); + + /// + /// Hashed name of _Width shader parameter. + /// + public readonly int WidthId = Shader.PropertyToID("_Width"); + + /// + /// Hashed name of _Intensity shader parameter. + /// + public readonly int IntensityId = Shader.PropertyToID("_Intensity"); + + /// + /// Hashed name of _GaussSamples shader parameter. + /// + public readonly int GaussSamplesId = Shader.PropertyToID("_GaussSamples"); /// /// Gets or sets a that renders objects outlined with a solid while color. @@ -25,10 +58,130 @@ public class OutlineResources : ScriptableObject public Shader HPassShader; /// - /// Gets or sets a that renders outline around the mask, that was generated with (pass 2). + /// Gets or sets a that renders outline around the mask, that was generated with and (pass 2). /// public Shader VPassBlendShader; + /// + /// Gets a -based material. + /// + public Material RenderMaterial + { + get + { + if (_renderMaterial == null) + { + _renderMaterial = new Material(RenderShader) + { + name = "Outline - SimpleRender", + hideFlags = HideFlags.HideAndDontSave + }; + } + + return _renderMaterial; + } + } + + /// + /// Gets a -based material. + /// + public Material HPassMaterial + { + get + { + if (_hPassMaterial == null) + { + _hPassMaterial = new Material(HPassShader) + { + name = "Outline - HPassRender", + hideFlags = HideFlags.HideAndDontSave + }; + } + + return _hPassMaterial; + } + } + + /// + /// Gets a -based material. + /// + public Material VPassBlendMaterial + { + get + { + if (_vPassMaterial == null) + { + _vPassMaterial = new Material(VPassBlendShader) + { + name = "Outline - VPassBlendRender", + hideFlags = HideFlags.HideAndDontSave + }; + } + + return _vPassMaterial; + } + } + + /// + /// Gets a for . + /// + public MaterialPropertyBlock HPassProperties + { + get + { + if (_hPassProperties == null) + { + _hPassProperties = new MaterialPropertyBlock(); + } + + return _hPassProperties; + } + } + + /// + /// Gets a for . + /// + public MaterialPropertyBlock VPassBlendProperties + { + get + { + if (_vPassProperties == null) + { + _vPassProperties = new MaterialPropertyBlock(); + } + + return _vPassProperties; + } + } + + /// + /// Gets or sets a fullscreen triangle mesh. + /// + public Mesh FullscreenTriangleMesh + { + get + { + if (_fullscreenTriangleMesh == null) + { + _fullscreenTriangleMesh = new Mesh() + { + name = "Outline - FullscreenTriangle", + hideFlags = HideFlags.HideAndDontSave, + vertices = new Vector3[] { new Vector3(-1f, -1f, 0f), new Vector3(-1f, 3f, 0f), new Vector3( 3f, -1f, 0f) }, + triangles = new int[] {0, 1, 2 } + }; + + _fullscreenTriangleMesh.UploadMeshData(true); + } + + return _fullscreenTriangleMesh; + } + set + { + _fullscreenTriangleMesh = value; + } + } + /// /// Gets a value indicating whether the instance is in valid state. /// @@ -40,6 +193,24 @@ public bool IsValid } } + /// + /// Gets cached gauss samples for the specified outline . + /// + public float[] GetGaussSamples(int width) + { + if (_gaussSmples == null) + { + _gaussSmples = new float[OutlineRenderer.MaxWidth][]; + } + + if (_gaussSmples[width] == null) + { + _gaussSmples[width] = OutlineRenderer.GetGaussSamples(width, null); + } + + return _gaussSmples[width]; + } + /// /// Resets the resources to defaults. /// @@ -50,17 +221,6 @@ public void ResetToDefaults() VPassBlendShader = Shader.Find("UnityFx/Outline/VPassBlend"); } - /// - /// Gets a new instance for the resources. - /// - public OutlineMaterialSet CreateMaterialSet() - { - if (_renderMaterial == null) - { - _renderMaterial = new Material(RenderShader); - } - - return new OutlineMaterialSet(this, _renderMaterial); - } + #endregion } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettings.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettings.cs index e9e0d76..7bb7cfe 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettings.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettings.cs @@ -27,6 +27,20 @@ public sealed class OutlineSettings : ScriptableObject, IOutlineSettings #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.OutlineMode == rhs.OutlineMode && + Mathf.Approximately(lhs.OutlineIntensity, rhs.OutlineIntensity); + } + #endregion #region IOutlineSettings @@ -84,5 +98,31 @@ public OutlineMode OutlineMode } #endregion + + #region IEquatable + + /// + public bool Equals(IOutlineSettings other) + { + return Equals(this, other); + } + + #endregion + + #region Object + + /// + public override bool Equals(object other) + { + return Equals(this, other as IOutlineSettings); + } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs index b8509da..cd90e9c 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs @@ -8,7 +8,7 @@ namespace UnityFx.Outline { [Serializable] - internal class OutlineSettingsInstance : IOutlineSettingsEx, IChangeTracking, IDisposable + internal class OutlineSettingsInstance : IOutlineSettingsEx, IChangeTracking { #region data @@ -28,35 +28,26 @@ internal class OutlineSettingsInstance : IOutlineSettingsEx, IChangeTracking, ID #pragma warning restore 0649 - private OutlineMaterialSet _materials; + private OutlineResources _resources; private bool _changed = true; #endregion #region interface - public OutlineMaterialSet OutlineMaterials + public OutlineResources OutlineResources { get { - return _materials; + return _resources; } } internal void SetResources(OutlineResources resources) { - if (resources == null) + if (resources != _resources) { - if (_materials != null) - { - _materials.Dispose(); - _materials = null; - } - } - else if (_materials == null || _materials.OutlineResources != resources) - { - _materials = resources.CreateMaterialSet(); - _materials.Reset(this); + _resources = resources; _changed = true; } } @@ -65,20 +56,15 @@ internal void UpdateChanged() { if (_outlineSettings != null) { - _outlineColor = _outlineSettings.OutlineColor; - _outlineWidth = _outlineSettings.OutlineWidth; - _outlineIntensity = _outlineSettings.OutlineIntensity; - _outlineMode = _outlineSettings.OutlineMode; - } - - if (_materials != null) - { - if (_outlineColor != _materials.OutlineColor || - _outlineWidth != _materials.OutlineWidth || - _outlineIntensity != _materials.OutlineIntensity || - _outlineMode != _materials.OutlineMode) + if (_outlineColor != _outlineSettings.OutlineColor || + _outlineWidth != _outlineSettings.OutlineWidth || + _outlineIntensity != _outlineSettings.OutlineIntensity || + _outlineMode != _outlineSettings.OutlineMode) { - _materials.Reset(this); + _outlineColor = _outlineSettings.OutlineColor; + _outlineWidth = _outlineSettings.OutlineWidth; + _outlineIntensity = _outlineSettings.OutlineIntensity; + _outlineMode = _outlineSettings.OutlineMode; _changed = true; } } @@ -106,12 +92,6 @@ public OutlineSettings OutlineSettings _outlineWidth = _outlineSettings.OutlineWidth; _outlineIntensity = _outlineSettings.OutlineIntensity; _outlineMode = _outlineSettings.OutlineMode; - - if (_materials != null) - { - _materials.Reset(this); - } - _changed = true; } } @@ -137,11 +117,6 @@ public Color OutlineColor { _outlineColor = value; _changed = true; - - if (_materials != null) - { - _materials.OutlineColor = value; - } } } } @@ -163,11 +138,6 @@ public int OutlineWidth { _outlineWidth = value; _changed = true; - - if (_materials != null) - { - _materials.OutlineWidth = value; - } } } } @@ -189,11 +159,6 @@ public float OutlineIntensity { _outlineIntensity = value; _changed = true; - - if (_materials != null) - { - _materials.OutlineIntensity = value; - } } } } @@ -213,11 +178,6 @@ public OutlineMode OutlineMode { _outlineMode = value; _changed = true; - - if (_materials != null) - { - _materials.OutlineMode = value; - } } } } @@ -243,15 +203,11 @@ public void AcceptChanges() #endregion - #region IDisposable + #region IEquatable - public void Dispose() + public bool Equals(IOutlineSettings other) { - if (_materials != null) - { - _materials.Dispose(); - _materials = null; - } + return OutlineSettings.Equals(this, other); } #endregion diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass1.shader b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass1.shader index e617203..737bf8c 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass1.shader +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass1.shader @@ -12,19 +12,20 @@ Shader "UnityFx/Outline/HPass" SubShader { + Cull Off ZWrite Off ZTest Always Lighting Off Pass { - CGPROGRAM + HLSLPROGRAM - #pragma vertex vert - #pragma fragment frag + #pragma vertex Vert + #pragma fragment Frag #include "UnityCG.cginc" - sampler2D _MaskTex; + UNITY_DECLARE_TEX2D(_MaskTex); float2 _MaskTex_TexelSize; int _Width; float _GaussSamples[32]; @@ -35,17 +36,17 @@ Shader "UnityFx/Outline/HPass" float2 uvs : TEXCOORD0; }; - v2f vert(appdata_base v) + v2f Vert(appdata_base v) { v2f o; - o.pos = UnityObjectToClipPos(v.vertex); + o.pos = float4(v.vertex.xy, 0.0, 1.0); o.uvs = ComputeScreenPos(o.pos); return o; } - float frag(v2f i) : COLOR + float Frag(v2f i) : COLOR { float TX_x = _MaskTex_TexelSize.x; float intensity; @@ -53,13 +54,13 @@ Shader "UnityFx/Outline/HPass" for (int k = -n; k <= n; k += 1) { - intensity += tex2D(_MaskTex, i.uvs.xy + float2(k * TX_x, 0)).r * _GaussSamples[abs(k)]; + intensity += UNITY_SAMPLE_TEX2D(_MaskTex, i.uvs.xy + float2(k * TX_x, 0)).r * _GaussSamples[abs(k)]; } return intensity; } - ENDCG + ENDHLSL } } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass2.shader b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass2.shader index 3221d7a..5b46ff2 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass2.shader +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlinePass2.shader @@ -14,19 +14,24 @@ Shader "UnityFx/Outline/VPassBlend" SubShader { + Cull Off + ZWrite Off + ZTest Always + Lighting Off + Blend SrcAlpha OneMinusSrcAlpha Pass { - CGPROGRAM + HLSLPROGRAM - #pragma vertex vert - #pragma fragment frag + #pragma vertex Vert + #pragma fragment Frag #include "UnityCG.cginc" - sampler2D _MaskTex; + UNITY_DECLARE_TEX2D(_MaskTex); float2 _MaskTex_TexelSize; - sampler2D _HPassTex; + UNITY_DECLARE_TEX2D(_HPassTex); float2 _HPassTex_TexelSize; float4 _Color; float _Intensity; @@ -39,19 +44,19 @@ Shader "UnityFx/Outline/VPassBlend" float2 uvs : TEXCOORD0; }; - v2f vert(appdata_base v) + v2f Vert(appdata_base v) { v2f o; - o.pos = UnityObjectToClipPos(v.vertex); + o.pos = float4(v.vertex.xy, 0.0, 1.0); o.uvs = ComputeScreenPos(o.pos); return o; } - float4 frag(v2f i) : COLOR + float4 Frag(v2f i) : COLOR { - if (tex2D(_MaskTex, i.uvs.xy).r > 0) + if (UNITY_SAMPLE_TEX2D(_MaskTex, i.uvs.xy).r > 0) { discard; } @@ -62,14 +67,14 @@ Shader "UnityFx/Outline/VPassBlend" for (int k = -n; k <= _Width; k += 1) { - intensity += tex2D(_HPassTex, i.uvs.xy + float2(0, k * TX_y)).r * _GaussSamples[abs(k)]; + intensity += UNITY_SAMPLE_TEX2D(_HPassTex, i.uvs.xy + float2(0, k * TX_y)).r * _GaussSamples[abs(k)]; } intensity = _Intensity > 99 ? step(0.01, intensity) : intensity * _Intensity; return float4(_Color.rgb, saturate(_Color.a * intensity)); } - ENDCG + ENDHLSL } } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlineRenderColor.shader b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlineRenderColor.shader index 2d81bf9..1b79f93 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlineRenderColor.shader +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Shaders/OutlineRenderColor.shader @@ -7,35 +7,37 @@ Shader "UnityFx/Outline/RenderColor" { SubShader { + Cull Off ZWrite Off ZTest Always Lighting Off Pass { - CGPROGRAM + HLSLPROGRAM - #pragma vertex vert - #pragma fragment frag + #pragma vertex Vert + #pragma fragment Frag + #include "UnityCG.cginc" struct v2f { float4 pos: POSITION; }; - v2f vert(v2f i) + v2f Vert(v2f i) { v2f o; o.pos = UnityObjectToClipPos(i.pos); return o; } - half4 frag(): COLOR0 + half4 Frag(): COLOR0 { return half4(1, 1, 1, 1); } - ENDCG + ENDHLSL } } } diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs deleted file mode 100644 index c35cf03..0000000 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (C) 2019 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 UnityEditor; -using UnityEngine; -using UnityEngine.TestTools; -using NUnit.Framework; - -namespace UnityFx.Outline -{ - [Category("OutlineMaterialSet"), TestOf(typeof(OutlineMaterialSet))] - public class OutlineMaterialSetTests : IOutlineSettingsTests, IDisposable - { - private OutlineResources _resources; - private OutlineMaterialSet _materialSet; - - [SetUp] - public void Init() - { - _resources = ScriptableObject.CreateInstance(); - _materialSet = _resources.CreateMaterialSet(); - Init(_materialSet); - } - - [TearDown] - public void Dispose() - { - _materialSet.Dispose(); - UnityEngine.Object.DestroyImmediate(_resources); - } - - [Test] - public void Dispose_CanBeCalledMultipleTimes() - { - _materialSet.Dispose(); - _materialSet.Dispose(); - } - - [Test] - public void Reset_ThrowsIfDisposed() - { - _materialSet.Dispose(); - - Assert.Throws(() => _materialSet.Reset(_materialSet)); - } - - [Test] - public void Reset_ThrowsIfArgumentIsNull() - { - Assert.Throws(() => _materialSet.Reset(null)); - } - - [Test] - public void Reset_SetsValuesPassed() - { - var settings = ScriptableObject.CreateInstance(); - - try - { - settings.OutlineColor = Color.yellow; - settings.OutlineWidth = 20; - settings.OutlineIntensity = 5; - settings.OutlineMode = OutlineMode.Blurred; - - _materialSet.Reset(settings); - - Assert.AreEqual(settings.OutlineColor, _materialSet.OutlineColor); - Assert.AreEqual(settings.OutlineWidth, _materialSet.OutlineWidth); - Assert.AreEqual(settings.OutlineIntensity, _materialSet.OutlineIntensity); - Assert.AreEqual(settings.OutlineMode, _materialSet.OutlineMode); - } - finally - { - UnityEngine.Object.DestroyImmediate(settings); - } - } - - [Test] - public void OutlineColor_ThrowsIfDisposed() - { - _materialSet.Dispose(); - - Assert.Throws(() => _materialSet.OutlineColor = Color.magenta); - } - - [Test] - public void OutlineColor_SetsMaterialParams() - { - var color = Color.blue; - _materialSet.OutlineColor = color; - - Assert.AreEqual(color, _materialSet.VPassBlendMaterial.GetColor(_materialSet.ColorNameId)); - } - - [Test] - public void OutlineWidth_ThrowsIfDisposed() - { - _materialSet.Dispose(); - - Assert.Throws(() => _materialSet.OutlineWidth = 10); - } - - [Test] - public void OutlineWidth_SetsMaterialParams() - { - var width = UnityEngine.Random.Range(OutlineRenderer.MinWidth, OutlineRenderer.MaxWidth); - _materialSet.OutlineWidth = width; - - Assert.AreEqual(width, _materialSet.HPassMaterial.GetInt(_materialSet.WidthNameId)); - Assert.AreEqual(width, _materialSet.VPassBlendMaterial.GetInt(_materialSet.WidthNameId)); - } - - [Test] - public void OutlineMode_SetsMaterialParams() - { - _materialSet.OutlineMode = OutlineMode.Solid; - Assert.AreEqual(OutlineRenderer.SolidIntensity, _materialSet.VPassBlendMaterial.GetFloat(_materialSet.IntensityNameId)); - } - - [Test] - public void OutlineIntensity_SetsMaterialParams() - { - var intensity = UnityEngine.Random.Range(OutlineRenderer.MinIntensity, OutlineRenderer.MaxIntensity); - - _materialSet.OutlineIntensity = intensity; - - Assert.AreEqual(intensity, _materialSet.VPassBlendMaterial.GetFloat(_materialSet.IntensityNameId)); - } - } -} diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs.meta b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs.meta deleted file mode 100644 index 8b04847..0000000 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineMaterialSetTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8dd20ed2fbac7544a91b914158581811 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineRendererTests.cs b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineRendererTests.cs index 137c1b1..fc2b70f 100644 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineRendererTests.cs +++ b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineRendererTests.cs @@ -40,8 +40,8 @@ public void Dispose_CanBeCalledMultipleTimes() [Test] public void RenderSingleObject_ThrowsIfNullArguments() { - Assert.Throws(() => _renderer.RenderSingleObject(default(IList), null)); - Assert.Throws(() => _renderer.RenderSingleObject(default(Renderer), null)); + Assert.Throws(() => _renderer.Render(default(IList), null, null)); + Assert.Throws(() => _renderer.Render(default(Renderer), null, null)); } } } diff --git a/Assets/Plugins/UnityFx.Outline/package.json b/Assets/Plugins/UnityFx.Outline/package.json index 006fc6a..696575f 100644 --- a/Assets/Plugins/UnityFx.Outline/package.json +++ b/Assets/Plugins/UnityFx.Outline/package.json @@ -1,7 +1,7 @@ { "name": "com.unityfx.outline", - "version": "0.5.0", - "displayName": "Screen-space outline for Unity", + "version": "0.7.0", + "displayName": "Screen-space outlines for Unity", "description": "Configurable per-object and per-camera outlines. Both solid and blurred outline modes are supported (Gauss blur). The outlines can be easily customized either through scripts or with Unity editor (both in edit-time or runtime).", "unity": "2017.2", "keywords": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc89a3..14e4872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/); this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.0] - 2019.11.26 + +`MaterialPropertyBlock`-based rendering and [Unity Post-processing Stack v2](https://github.com/Unity-Technologies/PostProcessing/tree/v2) compatibility. + +### Added +- Moved to for `MaterialPropertyBlock`-based rendering. This is in-line with Unity post-processing Stack and is more performant approach. +- Significant optimizations made to `OutlineRenderer`. + +### Changed +- `IOutlineSettings` now implements `IEquatable`. +- Changed all outline shaders to use HLSL-based macros. +- Modified all shaders to ignore MVP vertex transform to be compatible with the new rendering model. +- Exposed rendering APIs for `OutlineLayer` and `OutlineLayerCollection`. + +### Fixed +- Fixed `TiledGPUPerformanceWarning` on mobile targets. + +### Removed +- Removed `OutlineMaterialSet` class. It is not used in `MaterialPropertyBlock`-based effect rendering. + ## [0.6.0] - 2019.09.26 Quality of life improvements. diff --git a/README.md b/README.md index 6a6928d..15725b4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ Channel | UnityFx.Outline | Github | [![GitHub release](https://img.shields.io/github/release/Arvtesh/UnityFx.Outline.svg?logo=github)](https://github.com/Arvtesh/UnityFx.Outline/releases) Npm | [![Npm release](https://img.shields.io/npm/v/com.unityfx.outline.svg)](https://www.npmjs.com/package/com.unityfx.outline) ![npm](https://img.shields.io/npm/dt/com.unityfx.outline) -**Requires Unity 2017 or higher.** +**Requires Unity 2017 or higher.**
+**Compatible with [Unity Post-processing Stack v2](https://github.com/Unity-Technologies/PostProcessing/tree/v2).** ## Synopsis ![Outline demo](Docs/OutlineSamples.png "Outline demo") @@ -13,7 +14,7 @@ Npm | [![Npm release](https://img.shields.io/npm/v/com.unityfx.outline.svg)](htt *UnityFx.Outline* implements configurable per-object and per-camera outlines. Both solid and blurred outline modes are supported (Gauss blur). The outlines can be easily customized either through scripts or with Unity editor (both in edit-time or runtime). -Implementation is based on Unity [command buffers](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html), does not require putting objects into layers and has no dependencies. +Implementation is based on Unity [command buffers](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html), compatible with [Unity Post-processing Stack v2](https://github.com/Unity-Technologies/PostProcessing/tree/v2), extendable and has no external dependencies. Supported outline parameters are: - Color; @@ -22,8 +23,9 @@ Supported outline parameters are: - Intensity (for blurred outlines). Supported platforms: -- Windows standalone; -- More platforms to test. +- Windows/Mac standalone; +- Android; +- iOS. Please see [CHANGELOG](CHANGELOG.md) for information on recent changes. @@ -54,7 +56,7 @@ Npm package is available at [npmjs.com](https://www.npmjs.com/package/com.unityf } ], "dependencies": { - "com.unityfx.outline": "0.6.0" + "com.unityfx.outline": "0.7.0" } } ``` @@ -81,7 +83,7 @@ layer.Add(myGo); outlineEffect.OutlineLayers.Add(layer); ``` -This can be done at runtime or while editing a scene. If you choose to assign the script in runtime make sure `OutlineEffect.OutlineResources` is initialied. Disabling `OutlineEffect` script disables outlining for the camera (and frees all resources used). +This can be done at runtime or while editing a scene. If you choose to assign the script in runtime make sure `OutlineEffect.OutlineResources` is initialized. Disabling `OutlineEffect` script disables outlining for the camera (and frees all resources used). Multiple `OutlineEffect` scripts can share outline layers rendered. To achieve that assign the same layer set to all `OutlineEffect` instances: @@ -113,25 +115,63 @@ outlineBehaviour.OutlineIntensity = 10; There are a number of helper classes that can be used for writing highly customized outline implementations (if neither `OutlineBehaviour` nor `OutlineEffect` does not suit your needs). All outline implementations use following helpers: - `OutlineRenderer` is basically a wrapper around `CommandBuffer` for low-level outline rendering. -- `OutlineMaterialSet` is a set of materials used by `OutlineRenderer` for rendering. +- `OutlineSettings` is a set of outline settings. Using these helpers is quite easy to create new outline tools. For instance, the following code renders a blue outline around object the script is attached to in `myCamera`: ```csharp var commandBuffer = new CommandBuffer(); var renderers = GetComponentsInChildren(); -var materials = outlineResources.CreateMaterialSet(); -materials.OutlineColor = Color.blue; +// Any implementation of `IOutlineSettings` interface can be used here instead of `OutlineSettings`. +var settings = ScriptableObject.CreateInstance(); + +settings.OutlineColor = Color.blue; +settings.OutlineWidth = 12; + +// Get outline assets instance. In real app this usually comes from MonoBehaviour's serialized fields. +var resources = GetMyResources(); using (var renderer = new OutlineRenderer(commandBuffer, BuiltinRenderTextureType.CameraTarget)) { - renderer.RenderSingleObject(renderers, materials); + renderer.Render(renderers, resources, settings); } myCamera.AddCommandBuffer(OutlineRenderer.RenderEvent, commandBuffer); ``` +### Integration with Unity post-processing. + +The outline effect can easily be added to [Post-processing Stack v2](https://github.com/Unity-Technologies/PostProcessing/tree/v2). A minimal integration example is shown below: +```csharp +using System; +using UnityEngine; +using UnityEngine.Rendering.PostProcessing; +using UnityFx.Outline; + +[Serializable] +[PostProcess(typeof(OutlineEffectRenderer), PostProcessEvent.BeforeStack, "MyOutline", false)] +public sealed class Outline : PostProcessEffectSettings +{ + public OutlineResources OutlineResources; + public OutlineLayers OutlineLayers; +} + +public sealed class OutlineEffectRenderer : PostProcessEffectRenderer +{ + public override void Render(PostProcessRenderContext context) + { + using (var renderer = new OutlineRenderer(context.command, context.source, context.destination)) + { + settings.OutlineLayers.Render(renderer, settings.OutlineResources); + } + } +} +``` +For the sake of simplicity the sample does not include any kind of error checking and no editor integration provided. In real world app the `Outline` class should expose its data to Unity editor either via custom inspector or using parameter overrides. Also, there are quite a few optimizations missing (for example, resusing `RuntimeUtilities.fullscreenTriangle` value as `OutlineResources.FullscreenTriangleMesh`). + +More info on writing custom post processing effects can be found [here](https://docs.unity3d.com/Packages/com.unity.postprocessing@2.2/manual/Writing-Custom-Effects.html). + ## Motivation The project was initially created to help author with his [Unity3d](https://unity3d.com) projects. There are not many reusable open-source examples of it, so here it is. Hope it will be useful for someone. @@ -145,6 +185,7 @@ Please see the links below for extended information on the product: - [A great outline tutorial](https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/). - [Command buffers tutorial](https://lindenreid.wordpress.com/2018/09/13/using-command-buffers-in-unity-selective-bloom/). - [Gaussian blur tutorial](https://www.ronja-tutorials.com/2018/08/27/postprocessing-blur.html). +- [Excellent post-processing tutorial](https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/post-processing/). ## Contributing Please see [contributing guide](.github/CONTRIBUTING.md) for details.