├── LICENSE.txt ├── Outline ├── DrawSimple.shader ├── OutlinePostEffect.cs ├── PostOutline.shader └── SceneViewSelected.cginc └── README.md /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018-present Fredrik Westmark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Outline/DrawSimple.shader: -------------------------------------------------------------------------------- 1 | Shader "Custom/DrawSimple" 2 | { 3 | SubShader 4 | { 5 | // #0: things that are visible (pass depth). 1 in alpha, 1 in red (SM2.0) 6 | Pass 7 | { 8 | 9 | //One = The value of one - use this to let either the source or the destination color come through fully. 10 | //Zero = The value zero - use this to remove either the source or the destination values. 11 | Blend One Zero 12 | 13 | //Only render pixels whose reference value is less than or equal to the value in the buffer. 14 | ZTest LEqual 15 | 16 | //Off = Disables culling - all faces are drawn. Used for special effects. 17 | Cull Off 18 | 19 | //Controls whether pixels from this object are written to the depth buffer (default is On). If you’re drawng solid objects, leave this on. 20 | //If you’re drawing semitransparent effects, switch to ZWrite Off. For more details read below. 21 | ZWrite Off 22 | // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us 23 | Offset -0.02, 0 24 | CGPROGRAM 25 | #pragma vertex vert 26 | #pragma fragment frag 27 | #pragma target 2.0 28 | 29 | float _ObjectId = 1; 30 | #define DRAW_COLOR float4(1,1,1, 1) 31 | #include "SceneViewSelected.cginc" 32 | ENDCG 33 | } 34 | // #2: all the things, including the ones that fail the depth test. Additive blend, 1 in green, 1 in alpha (SM2.0) 35 | Pass 36 | { 37 | //Additive Blending 38 | Blend One One 39 | //Use the larger of source and destination. 40 | BlendOp Max 41 | //Always passes 42 | ZTest Always 43 | //Controls whether pixels from this object are written to the depth buffer (default is On). If you’re drawng solid objects, leave this on. 44 | //If you’re drawing semitransparent effects, switch to ZWrite Off. For more details read below. 45 | ZWrite Off 46 | //Off = Disables culling - all faces are drawn. Used for special effects. 47 | Cull Off 48 | //Set color channel writing mask. Writing ColorMask 0 turns off rendering to all color channels. 49 | //Default mode is writing to all channels (RGBA), but for some special effects you might want to leave certain channels unmodified, or disable color writes completely. 50 | ColorMask GBA 51 | // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us 52 | Offset -0.02, 0 53 | CGPROGRAM 54 | #pragma vertex vert 55 | #pragma fragment frag 56 | #pragma target 2.0 57 | float _ObjectId; 58 | #define DRAW_COLOR float4(0, 0, 1, 1) 59 | #include "SceneViewSelected.cginc" 60 | ENDCG 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /Outline/OutlinePostEffect.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class OutlinePostEffect : MonoBehaviour 5 | { 6 | Camera _mainCamera; 7 | Camera _maskCamera; 8 | RenderTexture _maskRT; 9 | RenderTexture _blurRT; 10 | 11 | public Shader Post_Outline; 12 | public Shader DrawSimple; 13 | public Color outlineColor = Color.green; 14 | Material _postMaterial; 15 | 16 | void Awake() 17 | { 18 | _blurRT = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.Default); 19 | _maskRT = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.Default); 20 | 21 | _mainCamera = GetComponent(); 22 | 23 | _maskCamera = new GameObject("MaskCamera").AddComponent(); 24 | _maskCamera.enabled = false; 25 | 26 | _postMaterial = new Material(Post_Outline); 27 | } 28 | void OnRenderImage(RenderTexture source, RenderTexture destination) 29 | { 30 | _maskCamera.CopyFrom(_mainCamera); 31 | _maskCamera.backgroundColor = Color.black; 32 | _maskCamera.clearFlags = CameraClearFlags.Nothing; 33 | 34 | _maskCamera.cullingMask = 1 << LayerMask.NameToLayer("Outline"); 35 | 36 | RenderTexture activeRT = RenderTexture.active; 37 | RenderTexture.active = _maskRT; 38 | 39 | GL.Clear(true, true, Color.clear); 40 | 41 | RenderTexture.active = activeRT; 42 | 43 | // 1. render selected objects into a mask buffer, with different colors for visible vs occluded ones (using existing Z buffer for testing) 44 | 45 | _maskCamera.targetTexture = _maskRT; 46 | //_maskCamera.SetTargetBuffers(_maskRT.colorBuffer, source.depthBuffer); 47 | _maskCamera.RenderWithShader(DrawSimple, ""); 48 | // 1. End 49 | 50 | // 2. blur the mask information in two separable passes, keeping the mask channels 51 | _postMaterial.SetVector("_BlurDirection", new Vector2(0, 1));//Vertical 52 | Graphics.Blit(_maskRT, _blurRT, _postMaterial, 0); 53 | _postMaterial.SetVector("_BlurDirection", new Vector2(1, 0));//Horizontal 54 | Graphics.Blit(_blurRT, _maskRT, _postMaterial, 0); 55 | // 2. End 56 | 57 | // 3.blend outline over existing scene image.blurred information &mask channels allow computing distance to selected 58 | // This is the #1: final postprocessing pass in PostOutline.shader. Right now i just substract mask channel(col.r) from blurred channel(col.b) to get the outline 59 | Shader.SetGlobalColor("_OutlineColor", outlineColor); 60 | Graphics.Blit(source, destination); 61 | Graphics.Blit(_maskRT, destination, _postMaterial, 1); 62 | 63 | _maskRT.DiscardContents(); 64 | _blurRT.DiscardContents(); 65 | } 66 | } -------------------------------------------------------------------------------- /Outline/PostOutline.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | 4 | Shader "Hidden/SceneViewSelected" 5 | { 6 | Properties 7 | { 8 | _MainTex ("Main Texture", 2D) = "white" {} 9 | _Cutoff ("Alpha cutoff", Range(0,1)) = 0.01 10 | } 11 | SubShader 12 | { 13 | CGINCLUDE 14 | #include "UnityCG.cginc" 15 | struct Input 16 | { 17 | float4 position : POSITION; 18 | float2 uv : TEXCOORD0; 19 | float4 projPos : TEXCOORD1; 20 | }; 21 | struct Varying 22 | { 23 | float4 position : SV_POSITION; 24 | float2 uv : TEXCOORD0; 25 | float4 projPos : TEXCOORD1; 26 | }; 27 | Varying vertex(Input input) 28 | { 29 | Varying output; 30 | output.position = UnityObjectToClipPos(input.position); 31 | output.uv = input.uv; 32 | output.projPos = ComputeScreenPos(output.position); 33 | return output; 34 | } 35 | ENDCG 36 | 37 | Tags { "RenderType"="Opaque" } 38 | // #0: separable blur pass, either horizontal or vertical 39 | Pass 40 | { 41 | ZTest Always 42 | Cull Off 43 | ZWrite Off 44 | 45 | CGPROGRAM 46 | #pragma vertex vertex 47 | #pragma fragment fragment 48 | #pragma target 2.0 49 | #include "UnityCG.cginc" 50 | float2 _BlurDirection = float2(1,0); 51 | sampler2D _MainTex; 52 | float4 _MainTex_TexelSize; 53 | // 9-tap Gaussian kernel, that blurs green & blue channels, 54 | // keeps red & alpha intact. 55 | static const half4 kCurveWeights[9] = { 56 | half4(0,0.0204001988,0.0204001988,0), 57 | half4(0,0.0577929595,0.0577929595,0), 58 | half4(0,0.1215916882,0.1215916882,0), 59 | half4(0,0.1899858519,0.1899858519,0), 60 | half4(1,0.2204586031,0.2204586031,1), 61 | half4(0,0.1899858519,0.1899858519,0), 62 | half4(0,0.1215916882,0.1215916882,0), 63 | half4(0,0.0577929595,0.0577929595,0), 64 | half4(0,0.0204001988,0.0204001988,0) 65 | }; 66 | half4 fragment(Varying i) : SV_Target 67 | { 68 | float2 step = _MainTex_TexelSize.xy * _BlurDirection; 69 | float2 uv = i.uv - step * 4; 70 | half4 col = 0; 71 | for (int tap = 0; tap < 9; ++tap) 72 | { 73 | col += tex2D(_MainTex, uv) * kCurveWeights[tap]; 74 | uv += step; 75 | } 76 | 77 | return col; 78 | } 79 | ENDCG 80 | } 81 | // #1: final postprocessing pass 82 | Pass 83 | { 84 | ZTest Always 85 | Cull Off 86 | ZWrite Off 87 | Blend SrcAlpha OneMinusSrcAlpha 88 | 89 | CGPROGRAM 90 | #pragma vertex vertex 91 | #pragma fragment fragment 92 | #pragma target 2.0 93 | #include "UnityCG.cginc" 94 | 95 | sampler2D _MainTex; 96 | float4 _MainTex_TexelSize; 97 | float3 _OutlineColor; 98 | 99 | half4 fragment(Varying i) : SV_Target 100 | { 101 | half4 col = tex2D(_MainTex, i.uv.xy); 102 | float saturateFac = 10; 103 | 104 | float alpha = saturate((col.b - col.r) * saturateFac); 105 | 106 | half4 outline = half4(_OutlineColor, alpha); 107 | 108 | return outline; 109 | } 110 | ENDCG 111 | } 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /Outline/SceneViewSelected.cginc: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_COLOR 2 | #define DRAW_COLOR 1 3 | #endif 4 | 5 | #include "UnityCG.cginc" 6 | 7 | sampler2D _MainTex; 8 | float4 _MainTex_ST; 9 | float _DoClip = 0; 10 | fixed _Cutoff = 1.0; 11 | 12 | struct appdata_t 13 | { 14 | float4 vertex : POSITION; 15 | float2 texcoord : TEXCOORD0; 16 | }; 17 | 18 | struct v2f 19 | { 20 | float4 vertex : SV_POSITION; 21 | float2 texcoord : TEXCOORD0; 22 | }; 23 | 24 | v2f vert (appdata_t IN) 25 | { 26 | v2f OUT; 27 | OUT.vertex = UnityObjectToClipPos(IN.vertex); 28 | OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex); 29 | return OUT; 30 | } 31 | 32 | fixed4 frag (v2f IN) : SV_Target 33 | { 34 | if (_DoClip) 35 | { 36 | fixed4 col = tex2D( _MainTex, IN.texcoord); 37 | clip(col.a - _Cutoff); 38 | } 39 | return DRAW_COLOR; 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unity-mesh-outline 2 | Unity Package for rendering outlines around meshes/objects 3 | 4 | ## Usage 5 | 6 | Either clone this repo, or download the latest \*.unitypackage from releases. 7 | 8 | Add the `OutlinePostEffect` to your main camera. Drag and drop the two shaders `DrawSimple` and `PostOutline` to their respective receptors on the newly added `OutlinePostEffect` component. 9 | 10 | ## Caveats 11 | 12 | * Currently the outline renders on top of everything else 13 | * There is no way(that I know of) to configure the outline width 14 | 15 | If you know how to fix any of the above(and make it configurable) I would love a PR. 16 | 17 | ## Credits 18 | 19 | All credit for this work goes to the participants in the following thread on the Unity3D forums: 20 | 21 | https://forum.unity.com/threads/can-we-use-new-unity-5-5-editor-outline-in-game.445885/ 22 | 23 | All I did was to bundle the code and make a few minor changes such as making the outline color configurable. 24 | --------------------------------------------------------------------------------