Search Unity

Resolved Shader to create an outline inside a polygon

Discussion in 'Shaders' started by Phil_42, Nov 20, 2020.

  1. Phil_42

    Phil_42

    Joined:
    Mar 15, 2017
    Posts:
    6
    Hey everyone

    I am trying to make a shader that creates an outline inside a polygon, like in screenshot 1. The polygons are two-dimensional and can have any shape since they are generated procedurally. I have achieved the effect in the first screenshot by creating mask texture as a Texture2D that I generate and then overlay on the polygon with a custom shader. However this is really really bad performance-wise (and looks ugly up-close) and I hope it could be done way faster with a shader.


    What I have achieved so far can be seen in the next screenshot.

    I made this with a very basic multipass shader with the following code. But it only creates the inside outline in one direction, which is not what I want.

    Code (CSharp):
    1. Shader "Custom/DistrictShader"
    2. {
    3.     Properties
    4.     {
    5.         _BaseColor("Base Color", Color) = (1,1,1,1)
    6.         _OutlineColor("Outline Color", Color) = (0,0,0,1)
    7.     }
    8.         SubShader
    9.     {
    10.         Pass
    11.         {
    12.             ZWrite Off
    13.             ColorMask 0 // do not render any color
    14.             Stencil {
    15.             Ref 1
    16.             Comp Always
    17.             Pass Replace
    18.             }
    19.  
    20.             CGPROGRAM
    21.             #pragma vertex vert
    22.             #pragma fragment frag
    23.  
    24.             #include "UnityCG.cginc"
    25.  
    26.             struct appdata
    27.             {
    28.                 float4 vertex:POSITION;
    29.                 float3 normal:NORMAL;
    30.                 float4 tangent:TANGENT;
    31.             };
    32.             struct v2f
    33.             {
    34.                 float4 clipPos:SV_POSITION;
    35.             };
    36.             v2f vert(appdata v)
    37.             {
    38.                 v2f o;
    39.                 o.clipPos = UnityObjectToClipPos(v.vertex * 0.98);
    40.                 return o;
    41.             }
    42.             fixed4 frag(v2f i) : SV_Target
    43.             {
    44.                 return fixed4(0,0,0,0);
    45.             }
    46.             ENDCG
    47.         }
    48.  
    49.  
    50.         Pass
    51.         {
    52.             ZWrite On
    53.  
    54.             CGPROGRAM
    55.             #pragma vertex vert
    56.             #pragma fragment frag
    57.  
    58.             #include "UnityCG.cginc"
    59.  
    60.             fixed4 _BaseColor;
    61.  
    62.             struct appdata
    63.             {
    64.                 float4 vertex:POSITION;
    65.             };
    66.             struct v2f
    67.             {
    68.                 float4 clipPos:SV_POSITION;
    69.             };
    70.             v2f vert(appdata v)
    71.             {
    72.                 v2f o;
    73.                 o.clipPos = UnityObjectToClipPos(v.vertex);
    74.                 return o;
    75.             }
    76.             fixed4 frag(v2f i) : SV_Target
    77.             {
    78.                 return _BaseColor;
    79.             }
    80.             ENDCG
    81.         }
    82.  
    83.         Pass
    84.         {
    85.             ZWrite Off
    86.             Stencil {
    87.             Ref 1
    88.             Comp NotEqual
    89.             }
    90.  
    91.             CGPROGRAM
    92.             #pragma vertex vert
    93.             #pragma fragment frag
    94.  
    95.             #include "UnityCG.cginc"
    96.  
    97.             float4 _OutlineColor;
    98.  
    99.             struct appdata
    100.             {
    101.                 float4 vertex:POSITION;
    102.             };
    103.             struct v2f
    104.             {
    105.                 float4 clipPos:SV_POSITION;
    106.             };
    107.             v2f vert(appdata v)
    108.             {
    109.                 v2f o;
    110.                 o.clipPos = UnityObjectToClipPos(v.vertex);
    111.                 return o;
    112.             }
    113.             fixed4 frag(v2f i) : SV_Target
    114.             {
    115.                 return _OutlineColor;
    116.             }
    117.             ENDCG
    118.         }
    119.     }
    120. }
    121.  
    Can anyone help me with this problem? The shader only has to support a base color and an outline color - no transparency or smooth transition. It doesn't even have to be affected by lighting.

    I have seen several resources for outlines but haven't really found anything for outlines that should be drawn inside the mesh. This thread (https://forum.unity.com/threads/outline-shader-with-outline-inside-of-mesh.921809/) describes a similar problem and if I understand it correctly it is not really possible the way I want to do it.
    My current solution (from screenshot 2) is based on this thread: https://forum.unity.com/threads/shader-for-adding-an-outline-to-the-inside-of-the-mesh.531288/

    I am very thankful for any help or ideas.
     
    WTFusername likes this.
  2. anthonytrianh

    anthonytrianh

    Joined:
    Dec 7, 2018
    Posts:
    6
    If your polygons are 2D then perhaps I have a similar effect you might want. My shader has 2 passes: an outline pass and an albedo pass. Here's the function for calculating the outline color (I use a custom .cginc file since my shader is quite long):

    Code (CSharp):
    1. inline fixed4 outline(float4 input_color, float2 input_texcoord, float4 outlineColor, float lineWidth) {
    2.     if (input_color.a != 0) {
    3.         fixed2 width = _MainTex_TexelSize.xy * lineWidth;
    4.  
    5.         // Get the neighbouring four pixels.
    6.         fixed4 pixelUp         = tex2D(_MainTex, input_texcoord.xy + float2(0, width.y));
    7.         fixed4 pixelDown    = tex2D(_MainTex, input_texcoord.xy - float2(0, width.y));
    8.         fixed4 pixelRight     = tex2D(_MainTex, input_texcoord.xy + float2(width.x, 0));
    9.         fixed4 pixelLeft        = tex2D(_MainTex, input_texcoord.xy - float2(width.x, 0));
    10.  
    11.         // If one of the neighbouring pixels is transparent, render as outline.
    12.         if (pixelUp.a * pixelDown.a * pixelRight.a * pixelLeft.a == 0) {
    13.             return fixed4(1, 1, 1, 1) * outlineColor;
    14.         }
    15.     }
    16.     return (1, 1, 1, 0);
    17. }
    input_color is simply the color sampled from the main texture used mainly to skip if the pixel is transparent (this makes it render outline inside the geometry rather than outside as it replaces color from the main texture). If the pixel is not transparent then we check its surrounding pixels (in all four directions), if the product of all four neighboring pixels' alphas equals 0 then we render it as an outline. You can control the thickness of the outline by changing lineWidth.

    Hope this helps!
     

    Attached Files:

    Phil_42 likes this.
  3. Phil_42

    Phil_42

    Joined:
    Mar 15, 2017
    Posts:
    6
    For others who might find this thread I'll post the approach I now use to handle this problem.



    1. I calculate the vectors I use for scaling the polygon correctly (red arrows in the screenshot)
    2. I pass them as tangents of the vertices in the polygon mesh.
    3. I created a two-pass shader that:
    • In a first pass draws the polygon normally in the color that the outline should have.
    • In a second pass I scale the polygon down along the tangent vectors and draw it in the color the polygon should have.
    4. The result is an outline on the inside of any polygon, where I can dynamically and performantly change the outline color and width through the shader which is what I wanted.

    The shader code ("Border" is what I called the "Outline"). The axis might be a bit confusing, this is because the polygons I work with are in 3D-Space on the xz-plane (like a floor).

    Code (CSharp):
    1. Shader "Custom/DistrictShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Color("Color", Color) = (1,1,1,1)
    7.  
    8.         _BorderColor("Border Color", Color) = (0,0,0,1)
    9.         _BorderWidth("Border Width", Range(0,0.2)) = 0.02
    10.  
    11.         _Blink("Blink", Float) = 0
    12.         _BlinkDuration("Blink Duration", Float) = 2
    13.         _BlinkColor("Blink Color", Color) = (1,1,1)
    14.     }
    15.     SubShader
    16.     {
    17.         Tags { "RenderType"="Opaque" }
    18.         LOD 100
    19.  
    20.         // 1. Pass: Paint normally in border color
    21.         Pass
    22.         {
    23.             CGPROGRAM
    24.             #pragma vertex vert
    25.             #pragma fragment frag
    26.  
    27.             #include "UnityCG.cginc"
    28.  
    29.             struct appdata
    30.             {
    31.                 float4 vertex : POSITION;
    32.                 float2 uv : TEXCOORD0;
    33.             };
    34.  
    35.             struct v2f
    36.             {
    37.                 float2 uv : TEXCOORD0;
    38.                 float4 vertex : SV_POSITION;
    39.             };
    40.  
    41.             float4 _BorderColor;
    42.  
    43.             // Build the object (goes through each vertex)
    44.             v2f vert (appdata IN)
    45.             {
    46.                 v2f OUT;
    47.                 OUT.vertex = UnityObjectToClipPos(IN.vertex);
    48.                 OUT.uv = IN.uv;
    49.                 return OUT;
    50.             }
    51.  
    52.             // Draw in the object (goes through each pixel)
    53.             fixed4 frag (v2f IN) : SV_Target
    54.             {
    55.                 return _BorderColor;
    56.             }
    57.             ENDCG
    58.         }
    59.  
    60.        
    61.         // 2. Pass: Shrink in tangent direction and paint normally
    62.         Pass
    63.         {
    64.             CGPROGRAM
    65.             #pragma vertex vert
    66.             #pragma fragment frag
    67.  
    68.             #include "UnityCG.cginc"
    69.  
    70.             struct appdata
    71.             {
    72.                 float4 vertex : POSITION;
    73.                 float4 tangent: TANGENT;
    74.                 float2 uv : TEXCOORD0;
    75.             };
    76.  
    77.             struct v2f
    78.             {
    79.                 float2 uv : TEXCOORD0;
    80.                 float4 vertex : SV_POSITION;
    81.             };
    82.  
    83.             sampler2D _MainTex;
    84.             float _BorderWidth;
    85.             float4 _Color;
    86.             float3 Offset = float3(0, 0, 0);
    87.             float _Blink;
    88.             float _BlinkDuration;
    89.             float3 _BlinkColor;
    90.  
    91.             // Build the object (goes through each vertex)
    92.             v2f vert(appdata IN)
    93.             {
    94.                 v2f OUT;
    95.                 float3 ver = IN.vertex.xyz;
    96.                 ver = ver + (_BorderWidth * IN.tangent) + float3(0, 0.001, 0);
    97.                 OUT.vertex = UnityObjectToClipPos(ver);
    98.                 OUT.uv = IN.uv;
    99.                 return OUT;
    100.             }
    101.  
    102.             // Draw in the object (goes through each pixel)
    103.             fixed4 frag(v2f IN) : SV_Target
    104.             {
    105.                 // sample the texture
    106.                 fixed4 col = tex2D(_MainTex, IN.uv);
    107.                 col = col * _Color;
    108.  
    109.                 if (_Blink == 1.0f)
    110.                 {
    111.                     float r = (0.5f + abs(sin(_Time.w * (1 / _BlinkDuration))));
    112.                     col *= r;
    113.                 }
    114.                 return col;
    115.             }
    116.             ENDCG
    117.         }
    118.     }
    119. }
    120.  
     
    Elektree likes this.
  4. sewy

    sewy

    Joined:
    Oct 11, 2015
    Posts:
    150
    Could you please by any chance share your code of calculation the new points from arbitrary polygon (possibly declared as list of points)?