Search Unity

What is the clip(tex2d).a value?

Discussion in 'Shaders' started by Erlend1, Jan 25, 2020.

  1. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    Inside UnityCG.gcinc there is a function for clipping pixels for Unity's crossfade:

    Code (CSharp):
    1.     sampler2D _DitherMaskLOD2D;
    2.     void UnityApplyDitherCrossFade(float2 vpos)
    3.     {
    4.         vpos /= 4;
    5.         vpos.y = frac(vpos.y) * 0.0625 /* 1/16 */ + unity_LODFade.y;
    6.         clip(tex2D(_DitherMaskLOD2D, vpos).a - 0.5);
    7.     }
    I try to understand how the clipping is done, but I haven't found any information regarding this A component of a 2d texture, and why it's subtracted by 0.5.

    I've discovered a strange bug where Unity's crossfade reveals an unclipped, non-transparent area where two objects overlap while the other parts fade correctly, and I wonder if this line has to be modified to fix this? The two objects use a modified standard shader.
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    The "a component" is the "Alpha" of the texture, RGBA. It's the channel that gives images their transparency.

    The
    clip()
    function will call
    discard
    if the value it receives is
    <= 0
    , when
    discard
    is called, the result of the fragment program is not output to the buffer, effectively "clipping" the result of this pixel, no depth, stencil, color, etc values will be output.

    So, what that line of code is saying is that if the sampled Alpha value of
    _DitherMaskLOD2D
    is below
    0.5
    (because if the result is above
    0.5
    , then subtracting
    0.5
    would not bring it down to
    <=0
    ), discard this result instead of rendering it.

    I'd have to see an image of what you mean for your issue though. The nature of dithered pixel clipping like this means that if two objects overlap, there's a chance their dither pattern could combine to create an opaque area, since they are not blending with semi-transparency it might not be as obvious.
     
    Last edited: Jan 25, 2020
  3. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    Hello, Invertex, thank you very much for your explanation.

    Here is a screenshot of the objects (in Editor mode):
    transparency_overlap_bug2.jpg

    This overlap only happens for a few "steps" while zooming towards the objects. It appears as if there is a third mushroom in the middle, but it actually is just two that are partly transparent. I believe you are right that they are somehow combining. I've experimented with different ZWrite values, material render queues, Blend values (doesn't seem to have an effect). Perhaps identifying which xy positions cause this non-clipped appearance could be useful, but debugging in shaders is very challenging...

    By the way, they use the same material and shader. The shader is more or less a standard shader, but with crossfade uncommented.
     
  4. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    Yeah that really looks like the crossfade pattern is just canceling eachother out. What value are you feeding into this function? It should be a screen-space position, not object or world-space value. If it was screen/clip space position, then the Y value going into the frac() would be consistent and objects wouldn't be able to have offset dither patterns that fill in eachother's gaps.
     
    Last edited: Jan 25, 2020
  5. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    Well, honestly I'm not sure what the coordinates are for. The full definition in the gcinc file is:

    Code (CSharp):
    1. #ifdef LOD_FADE_CROSSFADE
    2.     #define UNITY_APPLY_DITHER_CROSSFADE(vpos)  UnityApplyDitherCrossFade(vpos)
    3.     sampler2D _DitherMaskLOD2D;
    4.     void UnityApplyDitherCrossFade(float2 vpos)
    5.     {
    6.         vpos /= 4;
    7.         vpos.y = frac(vpos.y) * 0.0625 /* 1/16 */ + unity_LODFade.y;
    8.         clip(tex2D(_DitherMaskLOD2D, vpos).a - 0.5);
    9.     }.
    10. #else
    11.     #define UNITY_APPLY_DITHER_CROSSFADE(vpos)
    12. #endif
    I haven't figured out where "vpos" comes from. Perhaps it's explained here, but I thought it would be defined somewhere in a gcinc file?

    https://docs.unity3d.com/Manual/SL-ShaderSemantics.html

    "Screen space pixel position: VPOS
    A fragment shader can receive position of the pixel being rendered as a special VPOS semantic. This feature only exists starting with shader model 3.0, so the shader needs to have the #pragma target 3.0 compilation directive."​

    But this VPOS seems to have a r and g component too in that example, while the crossfade function only uses a float2.
     
    Last edited: Jan 25, 2020
  6. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    R and G are the same as X and Y. It's just semantics to make it more clear when you're dealing with colors or values.

    If you search up
    UNITY_APPLY_DITHER_CROSSFADE
    which as you see in that code, is the defined macro that then calls
    UnityApplyDitherCrossFade()
    , you'll find a bunch of cginc files showing how Unity uses this function. They feed it the
    ClipSpace
    position, which is calculated in the vertex program using
    UnityObjectToClipPos(v.vertex);
    .

    Are you using a custom surface or vert/frag shader? Or a built-in shader?
     
  7. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    I'm using the standard.shader with crossfade uncommented which I suppose is a built-in vertex shader? But I'm pretty sure it doesn't use #pragma surface.

    First pass (I've only uncommented crossfade in this pass as the other passes don't seem to affect the outcome):

    Code (CSharp):
    1.         Pass
    2.         {
    3.             Name "FORWARD"
    4.             Tags { "LightMode" = "ForwardBase" }
    5.  
    6.             Blend [_SrcBlend] [_DstBlend]
    7.             ZWrite [_ZWrite]
    8.  
    9.             CGPROGRAM
    10.             #pragma target 3.0
    11.  
    12.             // -------------------------------------
    13.  
    14.             #pragma shader_feature _NORMALMAP
    15.             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
    16.             #pragma shader_feature _EMISSION
    17.             #pragma shader_feature _METALLICGLOSSMAP
    18.             #pragma shader_feature ___ _DETAIL_MULX2
    19.             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
    20.             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
    21.             #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
    22.             #pragma shader_feature _PARALLAXMAP
    23.  
    24.             #pragma multi_compile_fwdbase
    25.             #pragma multi_compile_fog
    26.             #pragma multi_compile_instancing
    27.             // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
    28.             #pragma multi_compile _ LOD_FADE_CROSSFADE
    29.  
    30.             #pragma vertex vertBase
    31.             #pragma fragment fragBase
    32.             #include "UnityStandardCoreForward.cginc"
    33.  
    34.             ENDCG
    35.         }
     
  8. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    Standard Shader isn't really meant to be copied in that way. It's not ideal since it has all the passes exposed. That there also isn't really the shader code, the actual shader code is all in CGINC files that it references, so you'd have to go customize the Unity install's CGINC files to modify this shader properly, which isn't ideal.

    If you want Standard Shader that is easily customizeable, then Surface shader is the way to go. You simply define the properties you want for the material and which Standard shader properties you want to output to, like Albedo, Metalness, Smoothness, etc...

    https://docs.unity3d.com/Manual/SL-SurfaceShaders.html
    https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    There's a #pragma for surface shaders to enable the dither as seen on that first page:
    dithercrossfade
    or you can call the function yourself by feeding it the clip space position (and will also need
    #define LOD_FADE_CROSSFADE
    if you take that route)
     
    Last edited: Jan 25, 2020
  9. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    Okay, thanks for your suggestion. I have actually tried with a surface shader as you said, which has the same crossfade problem, unfortunately.

    Code (CSharp):
    1. Shader "Erlend/eSurface"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    7.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    8.         _Metallic ("Metallic", Range(0,1)) = 0.0
    9.      
    10.         // Blending state
    11.         [HideInInspector] _SrcBlend ("__src", Float) = 1.0
    12.         [HideInInspector] _DstBlend ("__dst", Float) = 0.0
    13.      
    14.     }
    15.     SubShader
    16.     {
    17.         Tags { "RenderType"="Opaque" "Queue"="Transparent"}
    18.         LOD 300
    19.  
    20.         CGPROGRAM
    21.         // Physically based Standard lighting model, and enable shadows on all light types
    22.         #pragma surface surf Standard fullforwardshadows dithercrossfade
    23.  
    24.         #pragma target 3.0 // Use shader model 3.0 target, to get nicer looking lighting
    25.  
    26.         sampler2D _MainTex;
    27.  
    28.         struct Input
    29.         {
    30.             float2 uv_MainTex;
    31.         };
    32.  
    33.         half _Glossiness;
    34.         half _Metallic;
    35.         fixed4 _Color;
    36.  
    37.         // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    38.         // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    39.         // #pragma instancing_options assumeuniformscaling
    40.         UNITY_INSTANCING_BUFFER_START(Props)
    41.             // put more per-instance properties here
    42.         UNITY_INSTANCING_BUFFER_END(Props)
    43.  
    44.         void surf (Input IN, inout SurfaceOutputStandard o)
    45.         {
    46.             // Albedo comes from a texture tinted by color
    47.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    48.             o.Albedo = c.rgb;
    49.             // Metallic and smoothness come from slider variables
    50.             o.Metallic = _Metallic;
    51.             o.Smoothness = _Glossiness;
    52.             o.Alpha = c.a;
    53.         }
    54.      
    55.         ENDCG
    56.     }
    57.     FallBack "Diffuse"
    58. }
    59.  
    Well, as both the native crossfades have the same issue, I'll try to get the clip space positions as you mentioned and then build the crossfade from there in the surface shader.
     
    Last edited: Jan 25, 2020
  10. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    Ok, I see the issue now. It's not the shader really. It's because one mushroom is only slightly further away, so the closer mushroom ends up having a dither pattern that is the opposite pattern of the further away mushroom which needs to use a less dense dither pattern:


    What you want to do is parent objects that are similar and close-by under an empty GameObject, and put the LOD Group component on that empty GameObject, you can then add all the child objects to each LOD stage and they will fade in unison so there is no clashing.

    (This could still happen if you have a lot of very close but separate LOD groups lined up in your view, in which case smaller Fade Transition Widths can help avoid that some more. But general rule, try to put really close groups of objects all in a single "LOD Group")
     
    Last edited: Jan 25, 2020
    Erlend1 likes this.
  11. Erlend1

    Erlend1

    Joined:
    Dec 18, 2019
    Posts:
    22
    Thanks for the ideas, Invertex. I'm thinking about how to implement it as the objects will eventually be randomly generated within a layer. When meshes are combined, the LODs fade without issues as you said, but I'd like to accommodate for LODs owned by single objects. However, it's apparent that I would need to add a check for object overlap and act accordingly - e.g. combine them and their LOD levels.
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    I mean, that isn’t a bug, that is the explicit behavior the cross fade was designed to have. The expectation is the two meshes you’re cross fading between are different LODs of the same object, and are very similar in overall shape and position. You want the perceived single object remain “solid” in the areas the two meshes overlap, and the areas outside of that fade in/out. If the two meshes you fade between are mostly the same silhouette it means the cross fade won’t be obvious. The alternative would be that during cross a fade a “solid” object would become semi-transparent. Imagine a wall where the two meshes are a finely detailed rock wall and the LOD is a box that covers the same space. If the object became semi-transparent you’d be able to see through the wall during the transition. The only real issue you’re having is the meshes you’re using as examples here aren’t lined up with each other, or you don’t actually want to be using the built in cross fade.

    It’s not the clip space position. The
    i.vertex
    value in the fragment shader is different than the value set in the vertex shader, and is no longer the clip space position. By the time that value gets to the fragment shader it’s the screen pixel position, exactly the same as
    VPOS
    . Indeed, using the
    SV_POSITION
    semantic as an input to the fragment shader is identical to using
    VPOS
    . The GPU is using the value in
    SV_POSITION
    to calculate the screen pixel position from the clip space position, and the final value the GPU calculates is passed along.
     
    Invertex likes this.