Search Unity

Question How to draw a sharp UI rectangle with an outline

Discussion in 'Shaders' started by Rowlan, May 2, 2021.

  1. Rowlan

    Rowlan

    Joined:
    Aug 4, 2016
    Posts:
    4,299
    I'm trying to draw a rectangle with an outline in a UI Image of a Canvas. However depending on the dimensions the outline isn't always drawn. It looks like this depending on the settings, here the right line is missing:

    img.png

    I read up about fwidth, ddx and ddy, but didn't manage to make it work. A Minimum Verifiable Example of my shader code (without fwidth) is this:

    Code (CSharp):
    1. Shader "RectangleTest"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.     SubShader
    7.     {
    8.          Tags{
    9.              "Queue" = "Overlay"
    10.          }
    11.  
    12.         Pass
    13.         {
    14.             Blend SrcAlpha OneMinusSrcAlpha
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.  
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct appdata
    22.             {
    23.                 float2 uv : TEXCOORD0;
    24.                 float4 vertex : POSITION;
    25.  
    26.             };
    27.  
    28.             struct v2f
    29.             {
    30.                 float2 uv : TEXCOORD0;
    31.                 float4 vertex : SV_POSITION;
    32.             };
    33.  
    34.  
    35.             v2f vert (appdata IN)
    36.             {
    37.                 v2f OUT;
    38.                 OUT.vertex = UnityObjectToClipPos(IN.vertex);
    39.                 OUT.uv = IN.uv;
    40.                 return OUT;
    41.             }
    42.  
    43.             fixed4 rectangle(v2f IN, float x, float y, fixed4 outerColor, fixed4 innerColor)
    44.             {
    45.                 float ratio = _ScreenParams.x / _ScreenParams.y;
    46.  
    47.                 float _RectangleSize = 0.02;
    48.                 float _RectangleBorder = 0.001;
    49.  
    50.                 // actual rectangle
    51.                 float minXOuter = x - _RectangleSize / ratio;
    52.                 float maxXOuter = x + _RectangleSize / ratio;
    53.                 float minYOuter = y - _RectangleSize;
    54.                 float maxYOuter = y + _RectangleSize;
    55.  
    56.                 float minXInner = x - (_RectangleSize - _RectangleBorder) / ratio;
    57.                 float maxXInner = x + (_RectangleSize - _RectangleBorder) / ratio;
    58.                 float minYInner = y - (_RectangleSize - _RectangleBorder);
    59.                 float maxYInner = y + (_RectangleSize - _RectangleBorder);
    60.  
    61.                 float uvx = IN.uv.x;
    62.                 float uvy = IN.uv.y;
    63.  
    64.                 fixed4 col = fixed4(0, 0, 0, 0);
    65.  
    66.                 // inner (excluding border)
    67.                 if (uvx >= minXInner && uvx <= maxXInner && uvy >= minYInner && uvy <= maxYInner)
    68.                 {
    69.                     col = innerColor;
    70.                 }
    71.                 // outer (including border)
    72.                 else if (uvx >= minXOuter && uvx <= maxXOuter && uvy >= minYOuter && uvy <= maxYOuter)
    73.                 {
    74.                     col = outerColor;
    75.                 }
    76.  
    77.                 return col;
    78.             }
    79.  
    80.             fixed4 frag (v2f IN) : SV_Target
    81.             {
    82.                 fixed4 col = rectangle( IN, 0.54, 0.5, fixed4(1,0,0,1), fixed4(0,0,1,1));
    83.  
    84.                 return col;
    85.             }
    86.             ENDCG
    87.         }
    88.     }
    89. }
    90.  
    What do I have to do to have the outline of the rectangle fully painted?

    What I'm trying to achieve is to have camera-like focus grids depending on the distance of a raycast (green outline = hit, green inner alpha = distance, white = no hit):

    focus.png
    Thank you very much for the help!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    0.001 is 1/1000. You're lucky you're getting any horizontal lines at all, let alone vertical lines.

    Code (csharp):
    1.             fixed4 rectangle(v2f IN, float x, float y, fixed4 outerColor, fixed4 innerColor)
    2.             {
    3.                 float _RectangleSize = 0.02;
    4.                 // fwidth gets you exactly 1 pixel width
    5.                 // note float2, not just a float
    6.                 float2 _RectangleBorder = fwidth(IN.uv);
    7.  
    8.                 // actual rectangle
    9.                 float minXOuter = x - _RectangleSize;
    10.                 float maxXOuter = x + _RectangleSize;
    11.                 float minYOuter = y - _RectangleSize;
    12.                 float maxYOuter = y + _RectangleSize;
    13.  
    14.                 // and get the border width for each axis
    15.                 float minXInner = x - (_RectangleSize - _RectangleBorder.x);
    16.                 float maxXInner = x + (_RectangleSize - _RectangleBorder.x);
    17.                 float minYInner = y - (_RectangleSize - _RectangleBorder.y);
    18.                 float maxYInner = y + (_RectangleSize - _RectangleBorder.y);
    19.  
    20.                 float uvx = IN.uv.x;
    21.                 float uvy = IN.uv.y;
    22.  
    23.                 fixed4 col = fixed4(0, 0, 0, 0);
    24.  
    25.                 // inner (excluding border)
    26.                 if (uvx >= minXInner && uvx <= maxXInner && uvy >= minYInner && uvy <= maxYInner)
    27.                 {
    28.                     col = innerColor;
    29.                 }
    30.                 // outer (including border)
    31.                 else if (uvx >= minXOuter && uvx <= maxXOuter && uvy >= minYOuter && uvy <= maxYOuter)
    32.                 {
    33.                     col = outerColor;
    34.                 }
    35.  
    36.                 return col;
    37.             }
     
    Rowlan likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You'll notice the ratio isn't in this version of the function anymore. That's because I pre-apply the ratio to the input UV in my code. Otherwise you'd need to multiply the
    _RectangleBorder.x
    by the aspect as the input UVs aren't yet scaled by it.
     
    Rowlan likes this.
  4. Rowlan

    Rowlan

    Joined:
    Aug 4, 2016
    Posts:
    4,299
    Thank you very much for your time and your help!!! It works :)

    focus.png
     
  5. Rowlan

    Rowlan

    Joined:
    Aug 4, 2016
    Posts:
    4,299
    How exactly did you do that? I tried this just for learning purposes, but I don't get the desired results.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    uv -= 0.5;
    uv.x *= aspect;
     
    Rowlan likes this.