Selection outline

Hi just migrated to Unity 5.5B and everything is working fine and seems stable so far.

I got 1 question about editor selection outline feature. I use similar feature in my game and custom shader for that.

If now its engine feature how can I access/use it ?

Thanks in advance!

2 Likes

Hi IkaBika,
You can find the options for this in the editor under “Preferences” → “Colors”.

But is it possible to use this run-time also? To highlight a gameobject ingame?

Lennart

1 Like

Thanks for quick answer . LeonhardP but I was referring to in-game use at runtime. I took quick look and shader pack but could not find reference.

Here are the two files we use:

This probably does too much compared to what you want to do (it does handling for picking and other things). But you can probably take this code and use it in your project with some modification.

Basically:
// Outline process:
// 1. render selected objects into a mask buffer, with different colors for visible vs occluded ones (using existing Z buffer for testing)
// 1a. objects also get an object ID which can be used to differntiate between differnt selection roots.
// 2. prepass on object id to discover edges between roots
// 2. blur the mask information in two separable passes, keeping the mask channels
// 3. blend outline over existing scene image. blurred information & mask channels allow computing distance to selected
// object edges, from which we create the outline pixels

SceneViewSelected.cginc

#ifndef DRAW_COLOR
#define DRAW_COLOR 1
#endif

#include "UnityCG.cginc"

sampler2D _MainTex;
float4 _MainTex_ST;
float _DoClip;
fixed _Cutoff;

struct appdata_t
{
    float4 vertex   : POSITION;
    float2 texcoord : TEXCOORD0;
};

struct v2f
{
    float4 vertex        : SV_POSITION;
    float2 texcoord      : TEXCOORD0;
};

v2f vert (appdata_t IN)
{
    v2f OUT;
    OUT.vertex = UnityObjectToClipPos(IN.vertex);
    OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex);
    return OUT;
}

fixed4 frag (v2f IN) : SV_Target
{
    if (_DoClip)
    {
        fixed4 col = tex2D( _MainTex, IN.texcoord);
        clip(col.a - _Cutoff);
    }
    return DRAW_COLOR;
}

SceneViewSelected.shader

Shader "Hidden/SceneViewSelected"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
        _Cutoff ("Alpha cutoff", Range(0,1)) = 0.01
    }

    SubShader
    {
        CGINCLUDE
        struct Input
        {
            float4 position : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct Varying
        {
            float4 position : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        Varying vertex(Input input)
        {
            Varying output;

            output.position = mul(UNITY_MATRIX_MVP, input.position);
            output.uv = input.uv;
            return output;
        }
        ENDCG
       
        Tags { "RenderType"="Opaque" }

        // #0: things that are visible (pass depth). 1 in alpha, 1 in red (SM2.0)
        Pass
        {
            Blend One Zero
            ZTest LEqual
            Cull Off
            ZWrite Off
            // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us
            Offset -0.02, 0

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            float _ObjectId;

            #define DRAW_COLOR float4(_ObjectId, 1, 1, 1)
            #include "SceneViewSelected.cginc"
            ENDCG
        }
        // #1: things that are visible (pass depth). 1 in alpha, 1 in red (SM3.0)
        Pass
        {
            Blend One Zero
            ZTest LEqual
            Cull Off
            ZWrite Off
            // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us
            Offset -0.02, 0

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            float _ObjectId;

            #define DRAW_COLOR float4(_ObjectId, 1, 1, 1)
            #include "SceneViewSelected.cginc"
            ENDCG
        }

        // #2: all the things, including the ones that fail the depth test. Additive blend, 1 in green, 1 in alpha (SM2.0)
        Pass
        {
            Blend One One
            BlendOp Max
            ZTest Always
            ZWrite Off
            Cull Off
            ColorMask GBA
            // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us
            Offset -0.02, 0

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            float _ObjectId;

            #define DRAW_COLOR float4(0, 0, 1, 1)
            #include "SceneViewSelected.cginc"
            ENDCG
        }

        // #3: all the things, including the ones that fail the depth test. Additive blend, 1 in green, 1 in alpha
        Pass
        {
            Blend One One
            BlendOp Max
            ZTest Always
            ZWrite Off
            Cull Off
            ColorMask GBA
            // push towards camera a bit, so that coord mismatch due to dynamic batching is not affecting us
            Offset -0.02, 0

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            float _ObjectId;

            #define DRAW_COLOR float4(0, 0, 1, 1)
            #include "SceneViewSelected.cginc"
            ENDCG
        }

        // #4: final postprocessing pass
        Pass
        {
            ZTest Always
            Cull Off
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
           
            CGPROGRAM
            #pragma vertex vertex
            #pragma fragment fragment
            #pragma target 3.0
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            half4 _OutlineColor;

            half4 fragment(Varying i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
               
                bool isSelected = col.a > 0.9;
                float alpha = saturate(col.b * 10);
                if (isSelected)
                {
                    // outline color alpha controls how much tint the whole object gets
                    alpha = _OutlineColor.a;
                    if (any(i.uv - _MainTex_TexelSize.xy*2 < 0) || any(i.uv + _MainTex_TexelSize.xy*2 > 1))
                        alpha = 1;
                }
                bool inFront = col.g > 0.0;
                if (!inFront)
                {
                    alpha *= 0.3;
                    if (isSelected) // no tinting at all for occluded selection
                        alpha = 0;
                }
                float4 outlineColor = float4(_OutlineColor.rgb,alpha);
                return outlineColor;
            }
            ENDCG
        }

        // #5: separable blur pass, either horizontal or vertical
        Pass
        {
            ZTest Always
            Cull Off
            ZWrite Off
           
            CGPROGRAM
            #pragma vertex vertex
            #pragma fragment fragment
            #pragma target 3.0
            #include "UnityCG.cginc"

            float2 _BlurDirection;
            sampler2D _MainTex;
            float4 _MainTex_TexelSize;

            // 9-tap Gaussian kernel, that blurs green & blue channels,
            // keeps red & alpha intact.
            static const half4 kCurveWeights[9] = {
                half4(0,0.0204001988,0.0204001988,0),
                half4(0,0.0577929595,0.0577929595,0),
                half4(0,0.1215916882,0.1215916882,0),
                half4(0,0.1899858519,0.1899858519,0),
                half4(1,0.2204586031,0.2204586031,1),
                half4(0,0.1899858519,0.1899858519,0),
                half4(0,0.1215916882,0.1215916882,0),
                half4(0,0.0577929595,0.0577929595,0),
                half4(0,0.0204001988,0.0204001988,0)
            };

            half4 fragment(Varying i) : SV_Target
            {
                float2 step = _MainTex_TexelSize.xy * _BlurDirection;
                float2 uv = i.uv - step * 4;
                half4 col = 0;
                for (int tap = 0; tap < 9; ++tap)
                {
                    col += tex2D(_MainTex, uv) * kCurveWeights[tap];
                    uv += step;
                }
                return col;
            }
            ENDCG
        }

        // #6: Compare object ids
        Pass
        {
            ZTest Always
            Cull Off
            ZWrite Off
           
            CGPROGRAM
            #pragma vertex vertex
            #pragma fragment fragment
            #pragma target 3.0
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;

            // 8 tap search around the current pixel to
            // see if it borders with an object that has a 
            // different object id
            static const half2 kOffsets[8] = {
                half2(-1,-1),
                half2(0,-1),
                half2(1,-1),
                half2(-1,0),
                half2(1,0),
                half2(-1,1),
                half2(0,1),
                half2(1,1)
            };

            half4 fragment(Varying i) : SV_Target
            {               
                float4 currentTexel = tex2D(_MainTex, i.uv);
                if (currentTexel.r == 0)
                    return currentTexel;

                // if the current texel borders with a 
                // texel that has a differnt object id
                // set the alpha to 0. This implies an
                // edge.
                for (int tap = 0; tap < 8; ++tap)
                {
                    float id = tex2D(_MainTex, i.uv + (kOffsets[tap] * _MainTex_TexelSize.xy)).r;
                    if (id != 0 && id - currentTexel.r != 0)
                    {
                        currentTexel.a = 0;
                    }
                }
                return currentTexel;
            }
            ENDCG
        }
    }
}
25 Likes

Oki thanks for sharing !

@Tim-C What is the best way to do step 1, it’s the only step that i cannot get it right/performant.
and let’s say, considering for mobile :smile:

1 Like