Search Unity

Mysterious hollow line in the center of a UI shader on iPad

Discussion in 'Shaders' started by Aldrick, Nov 20, 2018.

  1. Aldrick

    Aldrick

    Joined:
    Feb 19, 2014
    Posts:
    64
    I create this circular sector effect shader for UI,which is used on a RawImage component on Canvas.

    It work perfectly on Windows,Android,iPhone X and iPhone 6 Plus.But on iPad,there occurs a strange issue,there seems to be a tiny slice of a hollow area(about 1 or 2 pixels in width) in the horizontal center of this image,if that area is a rendered area which is not clipped by the shading instructions.

    Is this a problem of iPad or what?
    The images are zoomed in 300%.
    ui1.png ui2.png ui3.png

    The code is:

    Code (CSharp):
    1.  
    2.  
    3. Shader "Test/UI/MP_ui_sector"
    4. {
    5.     Properties
    6.     {
    7.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    8.         _Color ("Tint", Color) = (1,1,1,1)
    9.  
    10.         _StencilComp ("Stencil Comparison", Float) = 8
    11.         _Stencil ("Stencil ID", Float) = 0
    12.         _StencilOp ("Stencil Operation", Float) = 0
    13.         _StencilWriteMask ("Stencil Write Mask", Float) = 255
    14.         _StencilReadMask ("Stencil Read Mask", Float) = 255
    15.  
    16.         _ColorMask ("Color Mask", Float) = 15
    17.  
    18.         [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    19.         _Radian("Radian",RANGE(0.0,1.0)) = 0.5
    20.         _Sector("Sector",RANGE(0.0,0.1667))  = 0.1
    21.     }
    22.  
    23.     SubShader
    24.     {
    25.         Tags
    26.         {
    27.             "Queue"="Transparent"
    28.             "IgnoreProjector"="True"
    29.             "RenderType"="Transparent"
    30.             "PreviewType"="Plane"
    31.             "CanUseSpriteAtlas"="True"
    32.         }
    33.  
    34.         Stencil
    35.         {
    36.             Ref [_Stencil]
    37.             Comp [_StencilComp]
    38.             Pass [_StencilOp]
    39.             ReadMask [_StencilReadMask]
    40.             WriteMask [_StencilWriteMask]
    41.         }
    42.  
    43.         Cull Off
    44.         Lighting Off
    45.         ZWrite Off
    46.         ZTest [unity_GUIZTestMode]
    47.         Blend SrcAlpha OneMinusSrcAlpha
    48.         ColorMask [_ColorMask]
    49.  
    50.         Pass
    51.         {
    52.             Name "Default"
    53.             CGPROGRAM
    54.             #pragma vertex vert
    55.             #pragma fragment frag
    56.             #pragma target 2.0
    57.  
    58.             #include "UnityCG.cginc"
    59.             #include "UnityUI.cginc"
    60.  
    61.             #pragma multi_compile __ UNITY_UI_CLIP_RECT
    62.             #pragma multi_compile __ UNITY_UI_ALPHACLIP
    63.  
    64.             float _Radian;
    65.             float _Sector;
    66.  
    67.             struct appdata_t
    68.             {
    69.                 float4 vertex   : POSITION;
    70.                 float4 color    : COLOR;
    71.                 float2 texcoord : TEXCOORD0;
    72.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    73.             };
    74.  
    75.             struct v2f
    76.             {
    77.                 float4 vertex   : SV_POSITION;
    78.                 fixed4 color    : COLOR;
    79.                 float2 texcoord  : TEXCOORD0;
    80.                 float4 worldPosition : TEXCOORD1;
    81.                 float4 screenCoord : TEXCOORD2;
    82.                 UNITY_VERTEX_OUTPUT_STEREO
    83.             };
    84.  
    85.             sampler2D _MainTex;
    86.             fixed4 _Color;
    87.             fixed4 _TextureSampleAdd;
    88.             float4 _ClipRect;
    89.             float4 _MainTex_ST;
    90.  
    91.             v2f vert(appdata_t v)
    92.             {
    93.                 v2f OUT;
    94.                 UNITY_SETUP_INSTANCE_ID(v);
    95.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    96.                 OUT.worldPosition = v.vertex;
    97.                 OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
    98.  
    99.                 OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    100.                 OUT.screenCoord = ComputeScreenPos(OUT.vertex);
    101.                 OUT.color = v.color * _Color;
    102.                 return OUT;
    103.             }
    104.  
    105.             half4 frag(v2f IN) : SV_Target
    106.             {
    107.                 half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
    108.  
    109.                 float2 xAxis = float2(1.0,0.0);
    110.                 float uvX = IN.texcoord.x - 0.5;
    111.                 float uvY = IN.texcoord.y - (-3.5);
    112.  
    113.                 float tanYX = atan2(uvY,uvX);
    114.                 clip(tanYX - (_Radian - _Sector ) * 3.14159265);
    115.                 clip((_Radian + _Sector) * 3.14159265 - tanYX);
    116.  
    117.                 color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
    118.  
    119.                 #ifdef UNITY_UI_ALPHACLIP
    120.                 clip (color.a - 0.001);
    121.                 #endif
    122.  
    123.                 return color;
    124.             }
    125.         ENDCG
    126.         }
    127.     }
    128. }
    129.  
     
    Last edited: Nov 20, 2018
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I'd guess it's a problem with atan2. Trig functions on mobile devices tend to be very optimized ... and by very optimized I mean very approximate. It's entirely plausible that atan2 on the iPad is simply returning bogus values very close to zero degrees. The hacky work around would be to rotate by 90 degrees, ie: use atan2(Y,-X), then offset your angle tests.
     
  3. Aldrick

    Aldrick

    Joined:
    Feb 19, 2014
    Posts:
    64
    Thank you for replying.

    But the center line corresponds to the Pi /2 radian area,resulted from atan2(y,x).So flip x axis would be the same,right?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Woops, yep. I thought I'd read xy in your shader, not yx. Still going to blame atan2, but maybe the issue is at 90 degrees, so you could try xy instead?

    What I do for things like this is instead of calculating the angle with an arc tangent and comparing, I rotate the UV by the angle and test against the rotated UV's x. If you just need two edges this should be much more efficient.

    Code (CSharp):
    1. Shader "Unlit/Pie Slice"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    6.         _RadianAngleStart ("Slice Start", Range(-3.141592, 3.141592)) = -1
    7.         _RadianAngleEnd ("Slice End", Range(-3.141592, 3.141592)) = 1
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "RenderType"="Opaque" "PreviewType"="Plane" }
    12.         LOD 100
    13.  
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.            
    20.             #include "UnityCG.cginc"
    21.  
    22.             struct appdata
    23.             {
    24.                 float4 vertex : POSITION;
    25.                 float2 uv : TEXCOORD0;
    26.             };
    27.  
    28.             struct v2f
    29.             {
    30.                 float4 pos : SV_POSITION;
    31.                 float4 uv : TEXCOORD0;
    32.             };
    33.  
    34.             sampler2D _MainTex;
    35.  
    36.             float _RadianAngleStart, _RadianAngleEnd;
    37.            
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 o.pos = UnityObjectToClipPos(v.vertex);
    42.  
    43.                 float2 centeredUV = v.uv.xy * 2.0 - 1.0;
    44.  
    45.                 float minAngle = min(_RadianAngleStart, _RadianAngleEnd);
    46.                 float maxAngle = max(_RadianAngleStart, _RadianAngleEnd);
    47.  
    48.                 float s, c;
    49.                 sincos(minAngle, s, c);
    50.                 float edgeStart = centeredUV.x * c - centeredUV.y * s;
    51.  
    52.                 sincos(maxAngle, s, c);
    53.                 float edgeEnd = centeredUV.x * c - centeredUV.y * s;
    54.  
    55.                 o.uv = float4(v.uv.xy, edgeStart, -edgeEnd);
    56.  
    57.                 return o;
    58.             }
    59.            
    60.             fixed4 frag (v2f i) : SV_Target
    61.             {
    62.                 fixed4 col = tex2D(_MainTex, i.uv.xy);
    63.  
    64.                 bool over180 = abs(_RadianAngleStart - _RadianAngleEnd) > UNITY_PI;
    65.                 float slice = over180 ? max(i.uv.z, i.uv.w) : min(i.uv.z, i.uv.w);
    66.                 clip(slice);
    67.  
    68.                 return col;
    69.             }
    70.             ENDCG
    71.         }
    72.     }
    73. }
     
    Aldrick likes this.
  5. Aldrick

    Aldrick

    Joined:
    Feb 19, 2014
    Posts:
    64
    Hmm,I can get the transform part.The rotated basis vectors are (cosθ,sinθ) and (-sinθ,cosθ),then transformed x coord is (u·c - v ·s).But not quite understand the comparing x coords part.Why can the max or min of the transformed u(or x) coords determine the clipping?Mind elaborate it?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    When the angle difference is < 180 degrees, everywhere that either rotated UV's x is negative we want clipped. A min compare is essentially:
    if (x0 < 0.0 || x1 < 0.0) discard;

    When the angle difference is > 180 degrees, you what only the area that is negative in both to be clipped. Otherwise the visible slice just shrinks like before. Using a max compare is essentially:
    if (x0 < 0.0 && x1 < 0.0) discard;

    You could comment out the clip() in my shader and replace the return with the line below to visualize what's happening better.
    return(fixed4(step(0.0, slice), step(0.0, i.uv.z), step(0.0, i.uv.w), 1.0));

    For your use case, you can kind of ignore this since you'll never be showing more that 180 degrees.
     
  7. Aldrick

    Aldrick

    Joined:
    Feb 19, 2014
    Posts:
    64
    It looks you rotate the edge on each side of the middle line so it would be transformed to a upright space and the half with a x coord on the "out" side means clipped out.Neat!

    I might be trapped by the rotate direction.Initially I thought the positive rotate direction is clockwise.So if we roughly consider the start angle as a negative angle,it should make the uv rotate counterclockwise.That why I don't understand at first why doesn't your method seem to end up in a upright space or easy-to-see state.So it didn't add up for me.

    But as the result shows,your approach looks correct on PC,it means the positive direction of uv space must be counterclock wise.So a negative start angle in fact means rotate clockwise.It means the screen space obeys the left hand law,which equals to it has a z axis pointing along my eye sight into the screen.Is that right?
     
    Last edited: Nov 23, 2018
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Unity’s texture UVs use OpenGL’s coordinate orientation, which is to say bottom left is 0,0, top right is 1,1. Direct3D is top left 0,0, bottom right 1,1. When running Direct3D, (or really almost anything not OpenGL) Unity uploads the textures to the GPU upside down. This has the nice benefit of all textures, shaders and meshes just work with no modifications by the user... most of the time.

    Screen space is a totally different beast, and a completely different topic. At no time does screen space come into play with any of the rest of what this thread is discussing. Using sprites, or quads, or the UI system, these are all just view facing meshes with UVs, so the screen space is irrelevant. The only time screen space is an issue is when you’re doing post process effects which are directly reading and writing a render texture. Then all sorts of madness happens as the orientation flips depending on if you’re rendering to a render texture, or the frame buffer, and whether or not you have MSAA enabled. Why? I’m not sure anyone knows that.

    The Z direction also depends on a lot of factors, like where in the transform stage you are and when API you’re using. When in shader view space, your view is looking towards -Z, but in clip space it can be either +Z or -Z depending on if you’re using OpenGL or not.