├── Built-in Render Pipeline └── Assets │ ├── Outline │ ├── Outline.cs │ ├── OutlineEffect.cs │ └── OutlineQuickVolume.cs │ └── Resources │ └── Shaders │ ├── Dilate.shader │ ├── Outline.shader │ ├── SeparableBlur.shader │ └── Unlit.shader ├── README.md └── Universal Render Pipeline └── Assets ├── Outline ├── Outline.cs ├── OutlineFeature.cs └── OutlinePass.cs └── Resources └── Shaders ├── Dilate.shader ├── Outline.shader ├── SeparableBlur.shader └── UnlitColor.shader /Built-in Render Pipeline/Assets/Outline/Outline.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | public class Outline : MonoBehaviour 5 | { 6 | private OutlineQuickVolume m_outlineVolume; 7 | private float m_defaultSize; 8 | private float m_targetSize; 9 | private float m_alpha = 0; 10 | public float fadeSpeed = 5; 11 | public float highlightSpeed = 10; 12 | public float maxSize = 4; 13 | private void Awake() 14 | { 15 | tag = "Untagged"; 16 | m_outlineVolume = FindObjectOfType(); 17 | m_defaultSize = m_outlineVolume.size; 18 | } 19 | 20 | private void OnMouseEnter() 21 | { 22 | tag = "Outline"; 23 | StartCoroutine(FadeInColor()); 24 | } 25 | private void OnMouseDown() 26 | { 27 | StartCoroutine(OnClickHighlihgt()); 28 | } 29 | 30 | private void OnMouseExit() 31 | { 32 | StopAllCoroutines(); 33 | tag = "Untagged"; 34 | m_outlineVolume.size = m_defaultSize; 35 | } 36 | 37 | private IEnumerator FadeInColor() 38 | { 39 | m_alpha = 0; 40 | while (m_alpha < 1) 41 | { 42 | m_alpha = Mathf.MoveTowards(m_alpha, 1, fadeSpeed * Time.deltaTime); 43 | var c = m_outlineVolume.color; 44 | m_outlineVolume.color = new Color(c.r, c.g, c.b, m_alpha); 45 | yield return null; 46 | } 47 | } 48 | 49 | private IEnumerator OnClickHighlihgt() 50 | { 51 | m_targetSize = maxSize; 52 | while (true) 53 | { 54 | m_outlineVolume.size = Mathf.MoveTowards(m_outlineVolume.size, m_targetSize, highlightSpeed * Time.deltaTime); 55 | if (m_outlineVolume.size >= maxSize) m_targetSize = m_defaultSize; 56 | if (m_outlineVolume.size == m_defaultSize) break; 57 | yield return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Outline/OutlineEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEngine; 5 | using UnityEngine.Rendering; 6 | using UnityEngine.Rendering.PostProcessing; 7 | 8 | [Serializable] 9 | [PostProcess(typeof(OutlineRenderer), PostProcessEvent.AfterStack, "Custom/Outline")] 10 | public sealed class OutlineEffect : PostProcessEffectSettings 11 | { 12 | public ColorParameter color = new ColorParameter { value = Color.red }; 13 | [Range(0f, 5f), Tooltip("Outline size.")] 14 | public FloatParameter size = new FloatParameter { value = 2f }; 15 | [Range(0f, 1f), Tooltip("Outline softness.")] 16 | public FloatParameter softness = new FloatParameter { value = 0.5f }; 17 | public BoolParameter downSample = new BoolParameter { value = false }; 18 | public BoolParameter drawOnTop = new BoolParameter { value = true }; 19 | public ParameterOverride Tag = new ParameterOverride { value = "Outline" }; 20 | 21 | public override bool IsEnabledAndSupported(PostProcessRenderContext context) 22 | { 23 | //TODO: Find a smart way of filtering objects with outline 24 | bool objectsExist = false; 25 | try 26 | { 27 | objectsExist = GameObject.FindWithTag(Tag); 28 | } 29 | catch (Exception) { } 30 | return enabled.value && objectsExist; 31 | } 32 | } 33 | 34 | public sealed class OutlineRenderer : PostProcessEffectRenderer 35 | { 36 | private Material m_mat; 37 | private Shader m_SeparableBlurShader; 38 | private Shader m_DilateShader; 39 | private Shader m_OutlineShader; 40 | private int m_objectsID; 41 | private int m_outlineColorID; 42 | private int m_ztestID; 43 | private int m_blurredID; 44 | private int m_tempID; 45 | private int m_offsetsID; 46 | 47 | public override void Init() 48 | { 49 | m_mat = new Material(Shader.Find("Hidden/Outline/UnlitColor")); 50 | m_mat.hideFlags = HideFlags.HideAndDontSave; 51 | m_SeparableBlurShader = Shader.Find("Hidden/Outline/SeparableBlur"); 52 | m_DilateShader = Shader.Find("Hidden/Outline/Dilate"); 53 | m_OutlineShader = Shader.Find("Hidden/Outline"); 54 | m_objectsID = Shader.PropertyToID("_ObjectsTex"); 55 | m_outlineColorID = Shader.PropertyToID("_OutlineColor"); 56 | m_ztestID = Shader.PropertyToID("_ZTest"); 57 | m_blurredID = Shader.PropertyToID("_BlurredTex"); 58 | m_tempID = Shader.PropertyToID("_Temp"); 59 | m_offsetsID = Shader.PropertyToID("_Offsets"); 60 | } 61 | public override void Render(PostProcessRenderContext context) 62 | { 63 | //TODO: Find a smart way of filtering objects with outline 64 | var renderers = GameObject.FindGameObjectsWithTag(settings.Tag) 65 | .Select((g) => g.GetComponent()); 66 | context.command.GetTemporaryRT(m_objectsID, -1, -1, 24, FilterMode.Bilinear); 67 | var depthId = context.camera.actualRenderingPath == RenderingPath.Forward 68 | ? BuiltinRenderTextureType.Depth : BuiltinRenderTextureType.ResolvedDepth; 69 | context.command.SetRenderTarget(color: m_objectsID, depth: depthId); 70 | context.command.ClearRenderTarget(false, true, Color.clear); 71 | context.command.SetGlobalColor(m_outlineColorID, settings.color); 72 | 73 | int ztest = settings.drawOnTop ? (int)CompareFunction.Always : (int)CompareFunction.LessEqual; 74 | m_mat.SetInt(m_ztestID, ztest); 75 | // draw objects 76 | foreach (var r in renderers) 77 | { 78 | context.command.DrawRenderer(r, m_mat); 79 | } 80 | 81 | int sample = settings.downSample.value ? 2 : 1; 82 | context.command.GetTemporaryRT(m_blurredID, -sample, -sample, 0, FilterMode.Bilinear); 83 | context.command.GetTemporaryRT(m_tempID, -sample, -sample, 0, FilterMode.Bilinear); 84 | context.command.Blit(m_objectsID, m_blurredID); 85 | 86 | // horizontal dilate 87 | float dilateSize = settings.size * (1 - settings.softness); 88 | var dilate = context.propertySheets.Get(m_DilateShader); 89 | dilate.properties.SetVector(m_offsetsID, new Vector4(dilateSize / context.width, 0, 0, 0)); 90 | context.command.BlitFullscreenTriangle(m_blurredID, m_tempID, dilate, 0); 91 | // vertical dilate 92 | dilate.properties.SetVector(m_offsetsID, new Vector4(0, dilateSize / context.height, 0, 0)); 93 | context.command.BlitFullscreenTriangle(m_tempID, m_blurredID, dilate, 0); 94 | // horizontal blur 95 | float blurSize = settings.size - dilateSize; 96 | var blur = context.propertySheets.Get(m_SeparableBlurShader); 97 | blur.properties.SetVector(m_offsetsID, new Vector4(blurSize / context.width, 0, 0, 0)); 98 | context.command.BlitFullscreenTriangle(m_blurredID, m_tempID, blur, 0); 99 | // vertical blur 100 | blur.properties.SetVector(m_offsetsID, new Vector4(0, blurSize / context.height, 0, 0)); 101 | context.command.BlitFullscreenTriangle(m_tempID, m_blurredID, blur, 0); 102 | 103 | var outline = context.propertySheets.Get(m_OutlineShader); 104 | context.command.BlitFullscreenTriangle(context.source, context.destination, outline, 0); 105 | } 106 | public override void Release() 107 | { 108 | GameObject.DestroyImmediate(m_mat); 109 | base.Release(); 110 | } 111 | } -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Outline/OutlineQuickVolume.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Rendering.PostProcessing; 3 | 4 | [ExecuteInEditMode] 5 | public class OutlineQuickVolume : MonoBehaviour 6 | { 7 | private PostProcessVolume m_Volume; 8 | private OutlineEffect m_OutlineEffect; 9 | public Color color = Color.white; 10 | [Range(0,5)] 11 | public float size = 3f; 12 | [Range(0,1)] 13 | public float softness = 0.4f; 14 | public bool drawOnTop = true; 15 | public bool downSample = false; 16 | public string Tag = "Outline"; 17 | 18 | void OnEnable() 19 | { 20 | m_OutlineEffect = ScriptableObject.CreateInstance(); 21 | m_OutlineEffect.enabled.Override(true); 22 | m_OutlineEffect.color.Override(color); 23 | m_OutlineEffect.size.Override(size); 24 | m_OutlineEffect.softness.Override(softness); 25 | m_OutlineEffect.drawOnTop.Override(drawOnTop); 26 | m_OutlineEffect.downSample.Override(downSample); 27 | m_OutlineEffect.Tag.Override(Tag); 28 | m_Volume = PostProcessManager.instance.QuickVolume(gameObject.layer, 100f, m_OutlineEffect); 29 | } 30 | 31 | void Update() 32 | { 33 | m_OutlineEffect.size.value = size; 34 | m_OutlineEffect.softness.value = softness; 35 | m_OutlineEffect.color.value = color; 36 | m_OutlineEffect.drawOnTop.value = drawOnTop; 37 | m_OutlineEffect.downSample.value = downSample; 38 | m_OutlineEffect.Tag.value = Tag; 39 | } 40 | 41 | void OnDisable() 42 | { 43 | RuntimeUtilities.DestroyVolume(m_Volume, true, true); 44 | } 45 | } -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Resources/Shaders/Dilate.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/Dilate" { 2 | HLSLINCLUDE 3 | #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl" 4 | 5 | struct v2f { 6 | float4 pos : POSITION; 7 | float2 uv : TEXCOORD0; 8 | float4 uv01 : TEXCOORD1; 9 | float4 uv23 : TEXCOORD2; 10 | }; 11 | 12 | float4 _Offsets; 13 | v2f vert(AttributesDefault v) { 14 | v2f o; 15 | o.pos = float4(v.vertex.xy, 0.0, 1.0); 16 | o.uv = TransformTriangleVertexToUV(v.vertex.xy); 17 | #if UNITY_UV_STARTS_AT_TOP 18 | o.uv = o.uv * float2(1.0, -1.0) + float2(0.0, 1.0); 19 | #endif 20 | o.uv01 = o.uv.xyxy + _Offsets.xyxy * float4(1, 1, -1, -1); 21 | o.uv23 = o.uv.xyxy.xyxy + _Offsets.xyxy * float4(1, 1, -1, -1) * 2.0; 22 | return o; 23 | } 24 | 25 | TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex); 26 | 27 | half4 frag(v2f i) : COLOR{ 28 | half4 c1 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); 29 | half4 c2 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.xy); 30 | half4 c3 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.zw); 31 | half4 c4 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.xy); 32 | half4 c5 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.zw); 33 | c1 = max(c1, c2); 34 | c3 = max(c3, c4); 35 | c5 = max(c1, c5); 36 | return max(c3, c5); 37 | } 38 | ENDHLSL 39 | 40 | SubShader 41 | { 42 | Cull Off ZWrite Off ZTest Always 43 | Pass 44 | { 45 | HLSLPROGRAM 46 | #pragma vertex vert 47 | #pragma fragment frag 48 | ENDHLSL 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Resources/Shaders/Outline.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline" 2 | { 3 | HLSLINCLUDE 4 | #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl" 5 | 6 | TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex); 7 | TEXTURE2D_SAMPLER2D(_BlurredTex, sampler_BlurredTex); 8 | TEXTURE2D_SAMPLER2D(_ObjectsTex, sampler_ObjectsTex); 9 | float4 _OutlineColor; 10 | float4 Frag(VaryingsDefault i) : SV_Target 11 | { 12 | float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord); 13 | float4 objects = SAMPLE_TEXTURE2D(_ObjectsTex, sampler_ObjectsTex, i.texcoord); 14 | float4 blurred = SAMPLE_TEXTURE2D(_BlurredTex, sampler_BlurredTex, i.texcoord); 15 | float4 outline = saturate(blurred - objects); 16 | 17 | outline = outline.rgbr; 18 | outline.rgb *= _OutlineColor.rgb; 19 | outline *= _OutlineColor.a; 20 | return color * (1 - outline.a) + outline; 21 | } 22 | 23 | ENDHLSL 24 | 25 | SubShader 26 | { 27 | Cull Off ZWrite Off ZTest Always 28 | Pass 29 | { 30 | HLSLPROGRAM 31 | #pragma vertex VertDefault 32 | #pragma fragment Frag 33 | ENDHLSL 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Resources/Shaders/SeparableBlur.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/SeparableBlur" { 2 | HLSLINCLUDE 3 | #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl" 4 | 5 | struct v2f { 6 | float4 pos : POSITION; 7 | float2 uv : TEXCOORD0; 8 | float4 uv01 : TEXCOORD1; 9 | float4 uv23 : TEXCOORD2; 10 | float4 uv45 : TEXCOORD3; 11 | }; 12 | 13 | float4 _Offsets; 14 | v2f vert(AttributesDefault v) { 15 | 16 | v2f o; 17 | o.pos = float4(v.vertex.xy, 0.0, 1.0); 18 | o.uv = TransformTriangleVertexToUV(v.vertex.xy); 19 | 20 | #if UNITY_UV_STARTS_AT_TOP 21 | o.uv = o.uv * float2(1.0, -1.0) + float2(0.0, 1.0); 22 | #endif 23 | 24 | o.uv01 = o.uv.xyxy + _Offsets.xyxy * float4(1, 1, -1, -1); 25 | o.uv23 = o.uv.xyxy.xyxy + _Offsets.xyxy * float4(1, 1, -1, -1) * 2.0; 26 | o.uv45 = o.uv.xyxy.xyxy + _Offsets.xyxy * float4(1, 1, -1, -1) * 3.0; 27 | return o; 28 | } 29 | 30 | TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex); 31 | 32 | half4 frag(v2f i) : COLOR{ 33 | half4 color = float4 (0,0,0,0); 34 | color += 0.40 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); 35 | color += 0.15 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.xy); 36 | color += 0.15 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.zw); 37 | color += 0.10 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.xy); 38 | color += 0.10 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.zw); 39 | color += 0.05 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv45.xy); 40 | color += 0.05 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv45.zw); 41 | return color; 42 | } 43 | ENDHLSL 44 | 45 | SubShader 46 | { 47 | Cull Off ZWrite Off ZTest Always 48 | Pass 49 | { 50 | HLSLPROGRAM 51 | #pragma vertex vert 52 | #pragma fragment frag 53 | ENDHLSL 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Built-in Render Pipeline/Assets/Resources/Shaders/Unlit.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/UnlitColor" 2 | { 3 | Properties{ 4 | [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Z test", Int) = 8 5 | } 6 | SubShader 7 | { 8 | Pass 9 | { 10 | ZTest [_ZTest] 11 | ZWrite Off 12 | Fog { Mode off } 13 | CGPROGRAM 14 | #pragma vertex vert 15 | #pragma fragment frag 16 | #pragma fragmentoption ARB_precision_hint_fastest 17 | #include "UnityCG.cginc" 18 | 19 | float4 vert (float4 v : POSITION) : SV_Position 20 | { 21 | return UnityObjectToClipPos(v); 22 | } 23 | 24 | fixed4 frag (float4 i:SV_Position) : SV_Target 25 | { 26 | return 1; 27 | } 28 | ENDCG 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity object outlines 2 | 3 | ## Built-in render pipeline: 4 | The effect requires the `post-processing stack (v2)` package. To add the effect, click `Add effect...` 5 | on a `Post-Process Volume` component and select `Custom > Outline`, or add a `OutlineQuickVolume` 6 | component on an empty game object. All the objects with a specific tag (default tag is "Outline") 7 | will have the effect applied; 8 | 9 | ## Universal render pipeline: 10 | To add the effect, click `Add Render Feature` and select `Outline Feature` on the `Forward Renderer Data` 11 | asset. All the objects on a specific layers (chosen in the 12 | outline feature settings) will have the effect applied. 13 | 14 | ![Screenshot](https://user-images.githubusercontent.com/71973715/94941544-af48cf00-04dd-11eb-9e38-611864473ee2.png) 15 | 16 | ###### Limitations: 17 | All the objects are drawn into a single buffer, as a result all the properties 18 | of the outline are global. This means that you cannot have objects with different 19 | color, size outlines at the same time. 20 | 21 | For outlines with different properties multiple buffers can be drawn, or in the 22 | case of Universal render pipeline, multiple passes can be added, one for each set 23 | of properties. 24 | -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Outline/Outline.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | public class Outline : MonoBehaviour 6 | { 7 | private OutlineFeature m_outlineFeature; 8 | private float m_defaultSize; 9 | private float m_targetSize; 10 | private int m_defaultLayer; 11 | private int m_outlineLayer; 12 | private Color m_defaultColor; 13 | private float m_alpha = 0; 14 | 15 | public string defaultLayerName = "Default"; 16 | public string outlineLayerName = "Outline"; 17 | public float fadeSpeed = 5; 18 | public float highlightSpeed = 10; 19 | public float maxSize = 3; 20 | 21 | private void Awake() 22 | { 23 | m_outlineFeature = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 24 | if (!m_outlineFeature) 25 | { 26 | Debug.LogError("Outline feature not found."); 27 | return; 28 | } 29 | 30 | m_defaultLayer = LayerMask.NameToLayer(defaultLayerName); 31 | m_outlineLayer = LayerMask.NameToLayer(outlineLayerName); 32 | gameObject.layer = m_defaultLayer; 33 | } 34 | 35 | private void OnMouseEnter() 36 | { 37 | //Start outline fadein 38 | m_defaultSize = m_outlineFeature.settings.size; 39 | m_defaultColor = m_outlineFeature.settings.color; 40 | gameObject.layer = m_outlineLayer; 41 | StartCoroutine(FadeInColor()); 42 | } 43 | private void OnMouseDown() 44 | { 45 | StartCoroutine(OnClickHighlihgt()); 46 | } 47 | 48 | private void OnMouseExit() 49 | { 50 | //Reset outline to defaults 51 | StopAllCoroutines(); 52 | m_outlineFeature.settings.size = m_defaultSize; 53 | m_outlineFeature.settings.color = m_defaultColor; 54 | gameObject.layer = m_defaultLayer; 55 | } 56 | 57 | private IEnumerator FadeInColor() 58 | { 59 | m_alpha = 0; 60 | while (m_alpha < 1) 61 | { 62 | m_alpha = Mathf.MoveTowards(m_alpha, 1, fadeSpeed * Time.deltaTime); 63 | var c = m_outlineFeature.settings.color; 64 | m_outlineFeature.settings.color = new Color(c.r, c.g, c.b, m_alpha); 65 | yield return null; 66 | } 67 | } 68 | 69 | private IEnumerator OnClickHighlihgt() 70 | { 71 | m_targetSize = maxSize; 72 | while (true) 73 | { 74 | m_outlineFeature.settings.size = Mathf.MoveTowards(m_outlineFeature.settings.size, m_targetSize, highlightSpeed * Time.deltaTime); 75 | if (m_outlineFeature.settings.size >= maxSize) m_targetSize = m_defaultSize; 76 | if (m_outlineFeature.settings.size == m_defaultSize) break; 77 | yield return null; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Outline/OutlineFeature.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Rendering; 3 | using UnityEngine.Rendering.Universal; 4 | 5 | public class OutlineFeature : ScriptableRendererFeature 6 | { 7 | [System.Serializable] 8 | public class OutlineFeatureSettings 9 | { 10 | public string passTag = "OutlineFeature"; 11 | public RenderPassEvent Event = RenderPassEvent.BeforeRenderingPostProcessing; 12 | public LayerMask layerMask = 0; 13 | public Color color = Color.white; 14 | [Range(0, 5)] 15 | public float size = 2; 16 | [Range(0, 1)] 17 | public float softness = .5f; 18 | public bool drawOnTop = true; 19 | public bool downsample = false; 20 | } 21 | 22 | private OutlinePass.OutlineMaterials m_outlineMaterials = new OutlinePass.OutlineMaterials(); 23 | private OutlinePass m_OutlinePass; 24 | public OutlineFeatureSettings settings = new OutlineFeatureSettings(); 25 | 26 | public override void Create() 27 | { 28 | m_OutlinePass = new OutlinePass(settings.layerMask, m_outlineMaterials, name); 29 | m_OutlinePass.renderPassEvent = settings.Event; 30 | } 31 | public void OnEnable() => CreateMaterials(m_outlineMaterials); 32 | 33 | public void OnDisable() => DestroyMaterials(m_outlineMaterials); 34 | 35 | public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) 36 | { 37 | m_OutlinePass.settings = new OutlinePass.OutlineSettings 38 | { 39 | color = settings.color, 40 | size = settings.size, 41 | softness = settings.softness, 42 | downsample = settings.downsample, 43 | drawOnTop = settings.drawOnTop, 44 | }; 45 | 46 | RenderTargetIdentifier cameraTargetID = new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget); 47 | m_OutlinePass.cameraDepth = renderer.cameraDepth == cameraTargetID ? renderer.cameraColorTarget : renderer.cameraDepth; 48 | m_OutlinePass.cameraColorTarget = renderer.cameraColorTarget; 49 | 50 | renderer.EnqueuePass(m_OutlinePass); 51 | } 52 | private void CreateMaterials(OutlinePass.OutlineMaterials materials) 53 | { 54 | materials.UnlitMaterial = new Material(Shader.Find("Hidden/Outline/UnlitColor")); 55 | materials.UnlitMaterial.hideFlags = HideFlags.HideAndDontSave; 56 | materials.BlurMaterial = new Material(Shader.Find("Hidden/Outline/SeparableBlur")); 57 | materials.BlurMaterial.hideFlags = HideFlags.HideAndDontSave; 58 | materials.DilateMaterial = new Material(Shader.Find("Hidden/Outline/Dilate")); 59 | materials.DilateMaterial.hideFlags = HideFlags.HideAndDontSave; 60 | materials.OutlineMaterial = new Material(Shader.Find("Hidden/Outline")); 61 | materials.OutlineMaterial.hideFlags = HideFlags.HideAndDontSave; 62 | } 63 | private void DestroyMaterials(OutlinePass.OutlineMaterials materials) 64 | { 65 | DestroyImmediate(materials.UnlitMaterial); 66 | DestroyImmediate(materials.BlurMaterial); 67 | DestroyImmediate(materials.DilateMaterial); 68 | DestroyImmediate(materials.OutlineMaterial); 69 | } 70 | } -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Outline/OutlinePass.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.Rendering; 4 | using UnityEngine.Rendering.Universal; 5 | 6 | class OutlinePass : ScriptableRenderPass 7 | { 8 | public struct OutlineSettings 9 | { 10 | public Color color; 11 | public float size; 12 | public float softness; 13 | public bool drawOnTop; 14 | public bool downsample; 15 | } 16 | public class OutlineMaterials 17 | { 18 | public Material UnlitMaterial { get; set; } 19 | public Material BlurMaterial { get; set; } 20 | public Material DilateMaterial { get; set; } 21 | public Material OutlineMaterial { get; set; } 22 | } 23 | 24 | private readonly string m_ProfilerTag; 25 | private readonly ProfilingSampler m_ProfilingSampler; 26 | private readonly List m_ShaderTagIdList; 27 | private readonly OutlineMaterials m_materials; 28 | private RenderStateBlock m_RenderStateZTestOff; 29 | private RenderStateBlock m_RenderStateZTestOn; 30 | private FilteringSettings m_FilteringSettings; 31 | private RenderTargetHandle objectsID; 32 | private RenderTargetHandle blurredID; 33 | private RenderTargetHandle screenCopyID; 34 | private RenderTargetHandle tempID; 35 | 36 | public OutlineSettings settings; 37 | public RenderTargetIdentifier cameraColorTarget; 38 | public RenderTargetIdentifier cameraDepth; 39 | public OutlinePass(LayerMask layerMask, OutlineMaterials materials, string profilerTag) 40 | { 41 | m_ProfilerTag = profilerTag; 42 | m_ProfilingSampler = new ProfilingSampler(profilerTag); 43 | m_materials = materials; 44 | 45 | m_FilteringSettings = new FilteringSettings(RenderQueueRange.all, layerMask); 46 | 47 | m_ShaderTagIdList = new List 48 | { 49 | new ShaderTagId("SRPDefaultUnlit"), 50 | new ShaderTagId("UniversalForward"), 51 | new ShaderTagId("LightweightForward") 52 | }; 53 | 54 | m_RenderStateZTestOff = new RenderStateBlock(RenderStateMask.Nothing); 55 | m_RenderStateZTestOn = new RenderStateBlock(RenderStateMask.Depth) 56 | { 57 | depthState = new DepthState { writeEnabled = false, compareFunction = CompareFunction.LessEqual } 58 | }; 59 | } 60 | 61 | public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) 62 | { 63 | objectsID.Init("_ObjectsTex"); 64 | blurredID.Init("_BlurredTex"); 65 | screenCopyID.Init("_ScreenCopyTex"); 66 | tempID.Init("_Temp"); 67 | cmd.SetGlobalColor("_OutlineColor", settings.color); 68 | 69 | var descriptor = cameraTextureDescriptor; 70 | descriptor.depthBufferBits = 0; 71 | cmd.GetTemporaryRT(objectsID.id, descriptor); 72 | if (settings.downsample) 73 | { 74 | descriptor.width /= 2; 75 | descriptor.height /= 2; 76 | } 77 | cmd.GetTemporaryRT(blurredID.id, descriptor, FilterMode.Bilinear); 78 | cmd.GetTemporaryRT(tempID.id, descriptor, FilterMode.Bilinear); 79 | cmd.GetTemporaryRT(screenCopyID.id, cameraTextureDescriptor); 80 | 81 | ConfigureTarget(objectsID.id, cameraDepth); 82 | ConfigureClear(ClearFlag.Color, Color.clear); 83 | } 84 | 85 | public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) 86 | { 87 | var drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonOpaque); 88 | drawingSettings.overrideMaterial = m_materials.UnlitMaterial; 89 | drawingSettings.overrideMaterialPassIndex = 0; 90 | CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag); 91 | cmd.Clear(); 92 | 93 | using (new ProfilingScope(cmd, m_ProfilingSampler)) 94 | { 95 | RenderTextureDescriptor desc = renderingData.cameraData.cameraTargetDescriptor; 96 | var blurSize = settings.softness * settings.size; 97 | var dilateSize = settings.size - blurSize; 98 | var renderStateBlock = settings.drawOnTop ? m_RenderStateZTestOff : m_RenderStateZTestOn; 99 | context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings, ref renderStateBlock); 100 | 101 | Blit(cmd, colorAttachment, blurredID.id); 102 | // horizontal dilate 103 | cmd.SetGlobalVector("_OutlineOffsets", new Vector4( dilateSize / desc.width, 0, 0, 0)); 104 | Blit(cmd, blurredID.id, tempID.id, m_materials.DilateMaterial); 105 | // vertical dilate 106 | cmd.SetGlobalVector("_OutlineOffsets", new Vector4(0, dilateSize / desc.height, 0, 0)); 107 | Blit(cmd, tempID.id, blurredID.id, m_materials.DilateMaterial); 108 | // horizontal blur 109 | cmd.SetGlobalVector("_OutlineOffsets", new Vector4( blurSize / desc.width, 0, 0, 0)); 110 | Blit(cmd, blurredID.id, tempID.id, m_materials.BlurMaterial); 111 | // vertical blur 112 | cmd.SetGlobalVector("_OutlineOffsets", new Vector4(0, blurSize / desc.height, 0, 0)); 113 | Blit(cmd, tempID.id, blurredID.id, m_materials.BlurMaterial); 114 | Blit(cmd, cameraColorTarget, screenCopyID.id, m_materials.OutlineMaterial); 115 | Blit(cmd, screenCopyID.id, cameraColorTarget); 116 | } 117 | context.ExecuteCommandBuffer(cmd); 118 | CommandBufferPool.Release(cmd); 119 | } 120 | public override void FrameCleanup(CommandBuffer cmd) 121 | { 122 | cmd.ReleaseTemporaryRT(objectsID.id); 123 | cmd.ReleaseTemporaryRT(blurredID.id); 124 | cmd.ReleaseTemporaryRT(tempID.id); 125 | cmd.ReleaseTemporaryRT(screenCopyID.id); 126 | } 127 | } -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Resources/Shaders/Dilate.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/Dilate" { 2 | Properties 3 | { 4 | [HideInInspector] _MainTex("Texture", 2D) = "white" {} 5 | } 6 | CGINCLUDE 7 | #include "UnityCG.cginc" 8 | 9 | struct v2f { 10 | float4 pos : POSITION; 11 | float2 uv : TEXCOORD0; 12 | float4 uv01 : TEXCOORD1; 13 | float4 uv23 : TEXCOORD2; 14 | }; 15 | 16 | float4 _OutlineOffsets; 17 | sampler2D _MainTex; 18 | 19 | v2f vert (appdata_img v) { 20 | v2f o; 21 | o.pos = UnityObjectToClipPos(v.vertex); 22 | o.uv.xy = v.texcoord.xy; 23 | o.uv01 = v.texcoord.xyxy + _OutlineOffsets.xyxy * float4(1,1, -1,-1); 24 | o.uv23 = v.texcoord.xyxy + _OutlineOffsets.xyxy * float4(1,1, -1,-1) * 2.0; 25 | return o; 26 | } 27 | 28 | half4 frag (v2f i) : COLOR { 29 | half4 color = float4 (0,0,0,0); 30 | 31 | half4 c1 = tex2D (_MainTex, i.uv); 32 | half4 c2 = tex2D (_MainTex, i.uv01.xy); 33 | half4 c3 = tex2D (_MainTex, i.uv01.zw); 34 | half4 c4 = tex2D (_MainTex, i.uv23.xy); 35 | half4 c5 = tex2D (_MainTex, i.uv23.zw); 36 | c1 = max(c1, c2); 37 | c3 = max(c3, c4); 38 | c5 = max(c1, c5); 39 | return max(c3, c5); 40 | } 41 | 42 | ENDCG 43 | Subshader { 44 | Pass { 45 | ZTest Always Cull Off ZWrite Off 46 | CGPROGRAM 47 | #pragma fragmentoption ARB_precision_hint_fastest 48 | #pragma vertex vert 49 | #pragma fragment frag 50 | ENDCG 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Resources/Shaders/Outline.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | // No culling or depth 10 | Cull Off ZWrite Off ZTest Always 11 | 12 | Pass 13 | { 14 | CGPROGRAM 15 | #pragma vertex vert 16 | #pragma fragment frag 17 | 18 | #include "UnityCG.cginc" 19 | 20 | struct appdata 21 | { 22 | float4 vertex : POSITION; 23 | float2 uv : TEXCOORD0; 24 | }; 25 | 26 | struct v2f 27 | { 28 | float2 uv : TEXCOORD0; 29 | float4 vertex : SV_POSITION; 30 | }; 31 | 32 | v2f vert (appdata v) 33 | { 34 | v2f o; 35 | o.vertex = UnityObjectToClipPos(v.vertex); 36 | o.uv = v.uv; 37 | return o; 38 | } 39 | 40 | sampler2D _MainTex; 41 | sampler2D _ObjectsTex; 42 | sampler2D _BlurredTex; 43 | float4 _OutlineColor; 44 | float _Softness; 45 | float _Size; 46 | fixed4 frag (v2f i) : SV_Target 47 | { 48 | fixed4 screen = tex2D(_MainTex, i.uv); 49 | fixed4 objblur = tex2D(_BlurredTex, i.uv); 50 | fixed4 objects = tex2D(_ObjectsTex, i.uv); 51 | fixed4 outline = saturate(objblur - objects); 52 | outline = outline.rgbr; 53 | outline.rgb *= _OutlineColor.rgb; 54 | outline *= _OutlineColor.a; 55 | return screen* (1 - outline.a) + outline; 56 | } 57 | ENDCG 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Resources/Shaders/SeparableBlur.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/SeparableBlur" { 2 | Properties 3 | { 4 | [HideInInspector] _MainTex("Texture", 2D) = "white" {} 5 | } 6 | CGINCLUDE 7 | #include "UnityCG.cginc" 8 | 9 | struct v2f { 10 | float4 pos : POSITION; 11 | float2 uv : TEXCOORD0; 12 | float4 uv01 : TEXCOORD1; 13 | float4 uv23 : TEXCOORD2; 14 | float4 uv45 : TEXCOORD3; 15 | }; 16 | 17 | float4 _OutlineOffsets; 18 | sampler2D _MainTex; 19 | 20 | v2f vert (appdata_img v) { 21 | v2f o; 22 | o.pos = UnityObjectToClipPos(v.vertex); 23 | 24 | o.uv.xy = v.texcoord.xy; 25 | 26 | o.uv01 = v.texcoord.xyxy + _OutlineOffsets.xyxy * float4(1,1, -1,-1); 27 | o.uv23 = v.texcoord.xyxy + _OutlineOffsets.xyxy * float4(1,1, -1,-1) * 2.0; 28 | o.uv45 = v.texcoord.xyxy + _OutlineOffsets.xyxy * float4(1,1, -1,-1) * 3.0; 29 | 30 | return o; 31 | } 32 | 33 | half4 frag (v2f i) : COLOR { 34 | half4 color = float4 (0,0,0,0); 35 | color += 0.40 * tex2D (_MainTex, i.uv); 36 | color += 0.15 * tex2D (_MainTex, i.uv01.xy); 37 | color += 0.15 * tex2D (_MainTex, i.uv01.zw); 38 | color += 0.10 * tex2D (_MainTex, i.uv23.xy); 39 | color += 0.10 * tex2D (_MainTex, i.uv23.zw); 40 | color += 0.05 * tex2D (_MainTex, i.uv45.xy); 41 | color += 0.05 * tex2D (_MainTex, i.uv45.zw); 42 | return color; 43 | } 44 | ENDCG 45 | 46 | Subshader { 47 | Pass { 48 | ZTest Always Cull Off ZWrite Off 49 | CGPROGRAM 50 | #pragma fragmentoption ARB_precision_hint_fastest 51 | #pragma vertex vert 52 | #pragma fragment frag 53 | ENDCG 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Universal Render Pipeline/Assets/Resources/Shaders/UnlitColor.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/Outline/UnlitColor" 2 | { 3 | Properties{ 4 | [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Z test", Int) = 8 5 | } 6 | SubShader 7 | { 8 | Pass 9 | { 10 | ZTest[_ZTest] 11 | ZWrite Off 12 | Fog { Mode off } 13 | CGPROGRAM 14 | #pragma vertex vert 15 | #pragma fragment frag 16 | #pragma fragmentoption ARB_precision_hint_fastest 17 | #include "UnityCG.cginc" 18 | 19 | float4 vert(float4 v : POSITION) : SV_Position 20 | { 21 | return UnityObjectToClipPos(v); 22 | } 23 | fixed4 frag(float4 i:SV_Position) : SV_Target 24 | { 25 | return 1; 26 | } 27 | ENDCG 28 | } 29 | } 30 | } 31 | --------------------------------------------------------------------------------