Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Setting Min && Max 2d Outline size

Discussion in 'Shaders' started by P_e_t_a_c_h_e_k, Aug 7, 2021.

  1. P_e_t_a_c_h_e_k

    P_e_t_a_c_h_e_k

    Joined:
    Dec 26, 2017
    Posts:
    25
    Hi everyone) This shader was designed such that was defined in screen pixels width. But how do you set the maximum value for the outline size? I tried it like this:
    Code (CSharp):
    1. v2fOutline vertOutline (appdata_base v, float2 offset)
    2.         {
    3.             v2fOutline o;
    4.             o.pos = UnityObjectToClipPos(v.vertex);
    5.            // dist = length(o.pos.z - _WorldSpaceCameraPos.z); //length(WorldSpaceViewDir(v.vertex));
    6.             float OutlineSiz = clamp (_ScreenParams.xy, _OutlineMinWidth,_OutlineMaxWidth);
    7.             o.pos.xy += offset * 2 * o.pos.w * OutlineSiz;
    8.             o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    9.             return o;
    10.         }
    but at the same time I did not notice any limits ... Any ideas how to fix this? I really need your help(

    I'll post all the code just in case (no changes):
    Code (CSharp):
    1. Shader "Unlit/Spine Outline"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _OutlineColor ("Outline Color", Color) = (1,1,1,1)
    7.         _OutlineWidth ("Outline Width", Range(0, 4)) = 1
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    12.         LOD 100
    13.         ZWrite Off
    14.         Blend SrcAlpha OneMinusSrcAlpha
    15.         CGINCLUDE
    16.         #include "UnityCG.cginc"
    17.         sampler2D _MainTex;
    18.         float4 _MainTex_ST;
    19.         fixed4 _OutlineColor;
    20.         float _OutlineWidth;
    21.         struct v2fOutline
    22.         {
    23.             float4 pos : SV_POSITION;
    24.             float2 uv : TEXCOORD0;
    25.         };
    26.         v2fOutline vertOutline (appdata_base v, float2 offset)
    27.         {
    28.             v2fOutline o;
    29.             o.pos = UnityObjectToClipPos(v.vertex);
    30.             o.pos.xy += offset * 2 * o.pos.w * _OutlineWidth / _ScreenParams.xy;
    31.             o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    32.             return o;
    33.         }
    34.         fixed4 fragOutline (v2fOutline i) : SV_Target
    35.         {
    36.             fixed alpha = tex2D(_MainTex, i.uv).a;
    37.             fixed4 col = _OutlineColor;
    38.             col.a *= alpha;
    39.             return col;
    40.         }
    41.         ENDCG
    42.         Pass
    43.         {
    44.             CGPROGRAM
    45.             #pragma vertex vert
    46.             #pragma fragment fragOutline
    47.             v2fOutline vert (appdata_base v)
    48.             {
    49.                 return vertOutline(v, float2( 1, 1));
    50.             }
    51.             ENDCG
    52.         }
    53.         Pass
    54.         {
    55.             CGPROGRAM
    56.             #pragma vertex vert
    57.             #pragma fragment fragOutline
    58.             v2fOutline vert (appdata_base v)
    59.             {
    60.                 return vertOutline(v, float2(-1, 1));
    61.             }
    62.             ENDCG
    63.         }
    64.         Pass
    65.         {
    66.             CGPROGRAM
    67.             #pragma vertex vert
    68.             #pragma fragment fragOutline
    69.             v2fOutline vert (appdata_base v)
    70.             {
    71.                 return vertOutline(v, float2( 1,-1));
    72.             }
    73.             ENDCG
    74.         }
    75.         Pass
    76.         {
    77.             CGPROGRAM
    78.             #pragma vertex vert
    79.             #pragma fragment fragOutline
    80.             v2fOutline vert (appdata_base v)
    81.             {
    82.                 return vertOutline(v, float2(-1,-1));
    83.             }
    84.             ENDCG
    85.         }
    86.         Pass
    87.         {
    88.             CGPROGRAM
    89.             #pragma vertex vert
    90.             #pragma fragment frag
    91.             #include "UnityCG.cginc"
    92.             struct v2f
    93.             {
    94.                 float4 pos : SV_POSITION;
    95.                 float2 uv : TEXCOORD0;
    96.                 float4 color : TEXCOORD1;
    97.             };
    98.             v2f vert (appdata_full v)
    99.             {
    100.                 v2f o;
    101.                 o.pos = UnityObjectToClipPos(v.vertex);
    102.                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    103.                 o.color = v.color;
    104.                 return o;
    105.             }
    106.             fixed4 frag (v2f i) : SV_Target
    107.             {
    108.                 return tex2D(_MainTex, i.uv) * i.color;
    109.             }
    110.             ENDCG
    111.         }
    112.     }
    113. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    Please stop posting new threads each time. In the future, please just update the original thread.

    There are only a handful of us who actually respond to most of the questions in this community forum, and we're not always around. And we're not paid to do so. It might take a day or two sometimes.

    The short version of what you need to do is you'll need to calculate two "offset" scales, one constant in local space and one constant in screen space, and have the values be both be in the same space (either local or screen space).

    My original shader calculated it in screen space (actually clip space), and my modification I simplified down to being in local space as you want it to be constant in that space.

    You can calculate something similar to my local space outline width in clip space like this:
    Code (csharp):
    1. o.pos.xy += offset * _OutlineWidth * float2(_ScreenParams.x / _ScreenParams.y, 1.0);
    You want an outline that is constant in local space until it gets too small, at which point you want a minimum screen space width. To do that you'll want to take both total offsets and get the one that's the largest. Something like this:
    Code (csharp):
    1. o.pos.xy += offset * max(
    2.     _OutlineWidth * float2(_ScreenParams.x / _ScreenParams.y, 1.0),
    3.     _OutlineWidthMinPixels / _ScreenParams.xy
    4.     );
     
  3. P_e_t_a_c_h_e_k

    P_e_t_a_c_h_e_k

    Joined:
    Dec 26, 2017
    Posts:
    25
    Good evening. I tried your code. But it doesn't work. There is no maximum contour size ... Perhaps you do not quite understand what I want? I'll try to explain again:

    How does the original shader work? The closer the camera is, the less the outline is, the further the camera is, the larger the size of the outline. This is good, but I need to set a limit on the maximum size of the outline. Today I spent another one, this is already 12 days, on solving this problem, but again everything is in vain. I feel that I need to do this through the clamp, but I'm not sure anymore. I am 100% sure that it takes a couple of lines to solve my problem. Now I have code like this:
    Code (CSharp):
    1.  
    2.         v2fOutline vertOutline (appdata_base v, float2 offset)
    3.         {
    4.             v2fOutline o;
    5.             o.pos = UnityObjectToClipPos(v.vertex);
    6.             float  dist = length(o.pos.z - _WorldSpaceCameraPos.z);
    7.             float currentOutlineSize =  2 * o.pos.w * _OutlineWidth /_ScreenParams.xy;
    8.             float clampedOutlineSize = clamp(currentOutlineSize,_OutlineWidth,_MaxOutlineWidth);
    9.  
    10.            // o.pos.xy += offset * max(_OutlineWidth * float2(_ScreenParams.x / _ScreenParams.y, 1.0),100 / _ScreenParams.xy);
    11.             o.pos.xy += offset * clampedOutlineSize;
    12.             o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    13.             return o;
    14.         }
    where:
    _OutlineWidth - min;
    _MaxOutlineWidth - max
    I just need to stop the growth of the contour when its size is greater than the maximum size
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    In the original shader the outline does not change in size ... in screen space. It always produces an outline that is the specified outline width pixels wide.

    The
    o.pos
    is the homogeneous clip space position that the vertex shader outputs. WIthout going into too much detail, this is a value that the xy value range for what appears on screen is a -w to +w range, where w is the w component of the
    o.pos
    . So dividing that pixel scale "outline width" by the
    _ScreenParams.xy
    gets you a 0.0 to 1.0 range screen space scale, then multiplied
    2.0 * o.pos.w
    gets you the clip space scale for that number of pixels.

    My last suggestion did have two typos, which this should fix:
    Code (csharp):
    1.             o.pos.xy += offset * max(
    2.                 _OutlineWidth * float2(_ScreenParams.y / _ScreenParams.x, 1.0),
    3.                 2 * o.pos.w * _OutlineWidthPixel / _ScreenParams.xy
    4.                 );
    The behavior of that code should be as you get close to a sprite the outline will stay constant scale in world space. Again, as per my comments before, it's basically impossible with sprites to know how big they are so you'll have to adjust that outline width manually if you scale the sprite rather than just move the camera closer / further away. As you get further away if the world space outline is smaller than the pixel space outline, it'll never get any smaller than that pixel width.

    If you want the reverse behavior where the outline will never get larger than a specific pixel width, then change the
    max(
    to a
    min(
    . If you don't want the screen space width to be defined in pixels, but in screen widths, change the
    / _ScreenParams.xy
    to the same
    * float2(_ScreenParams.y / _ScreenParams.x, 1.0)
    as the other line, and remember you want really small values for the screen space outline width once you do so.
     
  5. P_e_t_a_c_h_e_k

    P_e_t_a_c_h_e_k

    Joined:
    Dec 26, 2017
    Posts:
    25
    It seems this is what I need! But what to change for? Change a ...? Perhaps you did not finish your thought?
    Now I realized that the clamp does not fit.

    In the picture, I tried to show what I need (there is a stroke with a size of 10, but the game will have 2. This is just an example) But here's how to write a piece of code ... For 2 hours I tried to work with your code somehow (different values, etc.), but there is still no stroke limit.
     

    Attached Files:

  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    Hmm ... I wonder if you're not seeing inlined code. Here's a screenshot of what I said as it appeared to me:
    upload_2021-8-9_10-37-58.png

    And here's that same text without the inlined code tags:
    If you want the reverse behavior where the outline will never get larger than a specific pixel width, then change the max( to a min(.

    That max( and min( appear in your quoted text, but not when you typed it out, so I have to assume those lines are not showing in your browser for some reason, maybe from auto translation or some kind of page theming override? That's going to make a lot of my posts much more confusing as I put any references to code inside inline code tags. And I do that a lot.


    One thing to be mindful of: the above technique for adding an outline to something is a cheap hack. It is not "a stroke" around the outside of the sprite like you might get from Photoshop or Gimp. Outlines any thicker than 1 sprite texel in width are going to have problems with sharp details like that hair tuft. This particular shader is only rendering the sprite mesh 4 extra times with an offset to fake an outline.

    The top and bottom of this image is grabbed from two different frames. The top from the point you say it should stop changing, and the bottom from near the end of the gif. The sprite is half the size, but the outline is exactly the same total width in screen pixels.
    upload_2021-8-9_11-2-9.png

    If you want it to continue to shrink with the sprite when zooming out, and never go beyond a max screen width when zooming in, use min().
     
  7. P_e_t_a_c_h_e_k

    P_e_t_a_c_h_e_k

    Joined:
    Dec 26, 2017
    Posts:
    25
    I really didn't see it ... For some reason, this part of the code was not displayed in the Microsoft Edge browser. But I went from Firefox browser and now everything is fine. So there on the topic of the code ... I tried with min and max. In the case of min, now _OutlineSize doesn't affect anything ... at all. In this case, everything depends on 10 (you had _OutlineWidthPixel), and again there are no limits. Even with min, the situation is the same as on the gif post above. In the case of max, _OutlineSize affects the size. But then again, there are no limits. Basically, as on the GIF post above (

    Are you sure about your code?

    Didn't notice ... I just like your original shader precisely because it perfectly bends around all, absolutely all irregularities. NONE of the shaders on github, youtube or just the web were that good. I just wanted to give it a limit for the maximum stroke, and it would just be 1000/100)

    Once again, this is what I need:
    , but in this case there should be some parameters in the "formula" (for example, _MaxOutlineSize or _ MaxPixelSize). Perhaps I just need to somehow find out the current "size" of the contour? Perhaps in this case, the pseudocode should be like this:

    if (currSize <maxSize)
    { // Make adjustments to the screen;
    }
    else {
    // Stop adjusting to the screen;
    }
    I just need the size of the pixels of the outline ("stroke" size) to be no larger than any value (1.0.1 or any other). As you said:
    Maybe I'm not writing clearly ... Or I don't understand something, but I just need to set a limit for the maximum stroke (
     
    Last edited: Aug 9, 2021
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    Yes I am very sure ... but because that doesn't mean I'm not stilll wrong I double checked ...

    ...and...

    ... I did miss something. My most recent code only works the way I intended when using a perspective camera. When using an orthographic camera as is common for 2D sprite rendering it doesn't quite work properly and the "world space" outlines are also just in screen space.

    Try this:
    Code (csharp):
    1.             o.pos = UnityObjectToClipPos(v.vertex);
    2.             float viewDepth = -UnityObjectToViewPos(v.vertex).z;
    3.             o.pos.xy += offset * min(
    4.                 2 * o.pos.w * _OutlineWidthPixel / _ScreenParams.xy,
    5.                 (o.pos.w / viewDepth) * _OutlineWidth * float2(_ScreenParams.y / _ScreenParams.x, 1.0)
    6.                 );
     
  9. P_e_t_a_c_h_e_k

    P_e_t_a_c_h_e_k

    Joined:
    Dec 26, 2017
    Posts:
    25
    Hello. I tried your code. Where is the actual maximum stroke limit? I mistook it for __OutlineWidthPixel. If so, then it seems that some kind of limit appears ... (that is, _OutlineWidth cannot be larger than __OutlineWidthPixel), but it works very strangely, now __OutlineWidth does not make a difference (what is 0.1, what is 1, what is 10, the stroke is the same) The size of the stroke depends only from __OutlineWidthPixel. Moreover, the problem of the "flower" has not disappeared anywhere, and in fact the maximum limit is needed just to limit what you see on the screen (copying the sprite in 8 sides, I understand how it works, but for this I just need a limit). And yes, thank you so much for trying to help, I really appreciate it, but so far the problem has not been resolved.

    I will try to rephrase. It is necessary to limit the "distance" of the copies of the sprite (if I can call it that, of course).
     

    Attached Files:

    Last edited: Aug 10, 2021