Search Unity

Is Alpha to coverage possible in Unity?

Discussion in 'Shaders' started by brisck1, May 7, 2016.

  1. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    I'm wondering if anyone knows if Unity 5 supports the Alpha to coverage technique as described here: http://www.humus.name/?page=3D&ID=61

    My goal is to achieve soft edged, anti-aliased alpha masks for foliage. I realize this is a forward rendering feature and requires MSAA, but it would be super nice to have in Unity. As of now, alpha masked textures just look aliased and horrible.

    I've seen some talk of enabling AlphaToMask in on the forums from a few years back but I've never seen any examples of it actually working nor have I managed to get it working myself.

    Any help of advice would be greatly appreciated!

    Thanks.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yes, it works, but it takes more than just adding AlphaToMask On to a surface shader. First it only works with anti-aliasing turned on so it doesn't work with deferred. Second it works well in a vertex & fragment shader but you'll need to modify individual the passes of a surface shader so you'll need to click on the "generate" button and modify the vertex & fragment shader a surface shader creates to get it to work. Only the forward base and add passes should use it. Everything else needs to be using clip().

    One more warning; it doesn't play well with realtime directional shadows. Because of the way Unity currently does the main directional light's shadows as a full screen pass using the depth buffer you can get odd shadow fringing depending on how you setup your shadowcaster pass. Additional shadows and light maps work fine though.
     
    Last edited: May 7, 2016
    brisck1 likes this.
  3. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    Thanks for the reply, interesting, I will give that a go and report back :)
     
  4. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    Ok I gave this a go by creating a simple unlit alpha masked shader (I used the built-in Unlit-AlphaTest.shader as a base) then adding in AlphaToMask On as suggested and recompiled the shader.

    At first, it appears to have worked, although not quite as expected:



    it looks softer from up close, and I can see various levels of transparency however it is still aliased around the edge of the mask - not quite what I was expecting in comparison to the images from the link in my original post.

    Am I perhaps doing something wrong in the shader code:

    Code (CSharp):
    1. // Unlit alpha-cutout shader.
    2. // - no lighting
    3. // - no lightmap support
    4. // - no per-material color
    5.  
    6. Shader "AlphaToCoverage/Alpha to Coverage Test" {
    7. Properties {
    8.     _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    9.     _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    10. }
    11. SubShader {
    12.     Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
    13.     LOD 100
    14.  
    15.     Lighting Off
    16.     AlphaToMask On
    17.  
    18.     Pass {
    19.         CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.             #pragma multi_compile_fog
    23.          
    24.             #include "UnityCG.cginc"
    25.  
    26.             struct appdata_t {
    27.                 float4 vertex : POSITION;
    28.                 float2 texcoord : TEXCOORD0;
    29.             };
    30.  
    31.             struct v2f {
    32.                 float4 vertex : SV_POSITION;
    33.                 half2 texcoord : TEXCOORD0;
    34.                 UNITY_FOG_COORDS(1)
    35.             };
    36.  
    37.             sampler2D _MainTex;
    38.             float4 _MainTex_ST;
    39.             fixed _Cutoff;
    40.  
    41.             v2f vert (appdata_t v)
    42.             {
    43.                 v2f o;
    44.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    45.                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    46.                 UNITY_TRANSFER_FOG(o,o.vertex);
    47.                 return o;
    48.             }
    49.          
    50.             fixed4 frag (v2f i) : SV_Target
    51.             {
    52.                 fixed4 col = tex2D(_MainTex, i.texcoord);
    53.                 clip(col.a - _Cutoff);
    54.                 UNITY_APPLY_FOG(i.fogCoord, col);
    55.                 return col;
    56.             }
    57.         ENDCG
    58.     }
    59. }
    60.  
    61. }
    62.  
    To clarify; I have Forward rendering enabled and MSAA set to 8x.

    Thanks again!
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Remove the clip(), or set the cutoff to something low like 0.1
     
    brisck1 likes this.
  6. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    Just tried setting the cutoff to 0.01 and it now looks a lot softer, thanks! :)


    Such a bummer that it doesn't work with dynamic directional shadows though :( Hopefully i'll be able to work around it somehow.

    So I have one last question left: I want to apply this to a shader built using Shader Forge (which doesn't currently support AlphaToMask) so I'm attempting to insert the AlphaToMask On line into the SF shader code in the same way as I did before, however it does not seem to work for some reason, could you take a look at this shader code and tell me what's going wrong?:

    Code (CSharp):
    1. // Shader created with Shader Forge v1.26
    2. // Shader Forge (c) Neat Corporation / Joachim Holmer - http://www.acegikmo.com/shaderforge/
    3. // Note: Manually altering this data may prevent you from opening it in Shader Forge
    4. /*SF_DATA;ver:1.26;sub:START;pass:START;ps:flbk:,iptp:0,cusa:False,bamd:0,lico:1,lgpr:1,limd:0,spmd:1,trmd:0,grmd:0,uamb:True,mssp:True,bkdf:False,hqlp:False,rprd:False,enco:False,rmgx:True,rpth:0,vtps:0,hqsc:True,nrmq:1,nrsp:0,vomd:0,spxs:False,tesm:0,olmd:1,culm:0,bsrc:0,bdst:1,dpts:2,wrdp:True,dith:0,rfrpo:True,rfrpn:Refraction,coma:15,ufog:False,aust:True,igpj:False,qofs:0,qpre:2,rntp:3,fgom:False,fgoc:False,fgod:False,fgor:False,fgmd:0,fgcr:0.5,fgcg:0.5,fgcb:0.5,fgca:1,fgde:0.01,fgrn:0,fgrf:300,stcl:False,stva:128,stmr:255,stmw:255,stcp:6,stps:0,stfa:0,stfz:0,ofsf:0,ofsu:0,f2p0:False,fnsp:False,fnfb:False;n:type:ShaderForge.SFN_Final,id:3138,x:32719,y:32712,varname:node_3138,prsc:2|emission-5813-RGB,clip-5813-A;n:type:ShaderForge.SFN_Tex2d,id:5813,x:32417,y:32868,ptovrint:False,ptlb:Base,ptin:_Base,varname:_Base,prsc:2,glob:False,taghide:False,taghdr:False,tagprd:False,tagnsco:False,tagnrm:False,tex:d6fcda50c6589554380ee9dbb4c702ea,ntxv:0,isnm:False;proporder:5813;pass:END;sub:END;*/
    5.  
    6. Shader "AlphaToCoverage/SFUnlit-AlphaCoverageTest" {
    7.     Properties {
    8.         _Base ("Base", 2D) = "white" {}
    9.         [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    10.     }
    11.     SubShader {
    12.         Tags {
    13.             "Queue"="AlphaTest"
    14.             "RenderType"="TransparentCutout"
    15.         }
    16.        
    17.         AlphaToMask On
    18.        
    19.         Pass {
    20.             Name "FORWARD"
    21.             Tags {
    22.                 "LightMode"="ForwardBase"
    23.             }
    24.            
    25.            
    26.             CGPROGRAM
    27.             #pragma vertex vert
    28.             #pragma fragment frag
    29.             #define UNITY_PASS_FORWARDBASE
    30.             #include "UnityCG.cginc"
    31.             #pragma multi_compile_fwdbase_fullshadows
    32.             #pragma exclude_renderers gles gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
    33.             #pragma target 3.0
    34.             uniform sampler2D _Base; uniform float4 _Base_ST;
    35.             struct VertexInput {
    36.                 float4 vertex : POSITION;
    37.                 float2 texcoord0 : TEXCOORD0;
    38.             };
    39.             struct VertexOutput {
    40.                 float4 pos : SV_POSITION;
    41.                 float2 uv0 : TEXCOORD0;
    42.             };
    43.             VertexOutput vert (VertexInput v) {
    44.                 VertexOutput o = (VertexOutput)0;
    45.                 o.uv0 = v.texcoord0;
    46.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
    47.                 return o;
    48.             }
    49.             float4 frag(VertexOutput i) : COLOR {
    50.                 float4 _Base_var = tex2D(_Base,TRANSFORM_TEX(i.uv0, _Base));
    51.                 clip(_Base_var.a - 0.5);
    52. ////// Lighting:
    53. ////// Emissive:
    54.                 float3 emissive = _Base_var.rgb;
    55.                 float3 finalColor = emissive;
    56.                 return fixed4(finalColor,1);
    57.             }
    58.             ENDCG
    59.         }
    60.         Pass {
    61.             Name "ShadowCaster"
    62.             Tags {
    63.                 "LightMode"="ShadowCaster"
    64.             }
    65.             Offset 1, 1
    66.            
    67.             CGPROGRAM
    68.             #pragma vertex vert
    69.             #pragma fragment frag
    70.             #define UNITY_PASS_SHADOWCASTER
    71.             #include "UnityCG.cginc"
    72.             #include "Lighting.cginc"
    73.             #pragma fragmentoption ARB_precision_hint_fastest
    74.             #pragma multi_compile_shadowcaster
    75.             #pragma exclude_renderers gles gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
    76.             #pragma target 3.0
    77.             uniform sampler2D _Base; uniform float4 _Base_ST;
    78.             struct VertexInput {
    79.                 float4 vertex : POSITION;
    80.                 float2 texcoord0 : TEXCOORD0;
    81.             };
    82.             struct VertexOutput {
    83.                 V2F_SHADOW_CASTER;
    84.                 float2 uv0 : TEXCOORD1;
    85.             };
    86.             VertexOutput vert (VertexInput v) {
    87.                 VertexOutput o = (VertexOutput)0;
    88.                 o.uv0 = v.texcoord0;
    89.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
    90.                 TRANSFER_SHADOW_CASTER(o)
    91.                 return o;
    92.             }
    93.             float4 frag(VertexOutput i) : COLOR {
    94.                 float4 _Base_var = tex2D(_Base,TRANSFORM_TEX(i.uv0, _Base));
    95.                 clip(_Base_var.a - 0.5);
    96.                 SHADOW_CASTER_FRAGMENT(i)
    97.             }
    98.             ENDCG
    99.         }
    100.     }
    101.     FallBack "Diffuse"
    102.     CustomEditor "ShaderForgeMaterialInspector"
    103. }
    104.  
    Thanks again!
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    So I use two tricks for this.

    The simple one is to use a hardcoded clip of 0.1 on the forward pass, and 0.8 on the shadowcaster pass. It doesn't fix the issue but mitigates the most egregious error of the bright edge.

    The more complex method requires overriding some of the cginc files to change how it samples the screen space shadows. See:
    http://forum.unity3d.com/threads/fixing-screen-space-directional-shadows-and-anti-aliasing.379902/

    For a project I'm working on I stipple the alpha in the shadow casting pass and the above bit of code produces slightly cleaner shadows with less fringing.


    For the SF shader try putting the line within the forward pass {} instead of above it and change the clip() in the forward pass to not be 0.5
     
    olli_vrcoaster and brisck1 like this.
  8. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    Thanks again for your help! :)

    I tried moving the AlphaToMask On to the Forward section of the shader but unfortunately it still does not work:

    Code (CSharp):
    1. Shader "AlphaToCoverage/SFUnlit-AlphaCoverageTest" {
    2.     Properties {
    3.         _Base ("Base", 2D) = "white" {}
    4.         _Cutoff ("Alpha cutoff", Range(0,1)) = 0.01
    5.     }
    6.     SubShader {
    7.         Tags {
    8.             "Queue"="AlphaTest"
    9.             "RenderType"="TransparentCutout"
    10.         }
    11.      
    12.         Pass {
    13.             Name "FORWARD"
    14.             Tags {
    15.                 "LightMode"="ForwardBase"
    16.             }
    17.          
    18.             AlphaToMask On
    19.          
    20.             CGPROGRAM
    21.             #pragma vertex vert
    22.             #pragma fragment frag
    23.             #define UNITY_PASS_FORWARDBASE
    24.             #include "UnityCG.cginc"
    25.             #pragma multi_compile_fwdbase_fullshadows
    26.             #pragma exclude_renderers gles gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
    27.             #pragma target 3.0
    28.             uniform sampler2D _Base; uniform float4 _Base_ST;
    29.             struct VertexInput {
    30.                 float4 vertex : POSITION;
    31.                 float2 texcoord0 : TEXCOORD0;
    32.             };
    33.             struct VertexOutput {
    34.                 float4 pos : SV_POSITION;
    35.                 float2 uv0 : TEXCOORD0;
    36.             };
    37.             VertexOutput vert (VertexInput v) {
    38.                 VertexOutput o = (VertexOutput)0;
    39.                 o.uv0 = v.texcoord0;
    40.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
    41.                 return o;
    42.             }
    43.             float4 frag(VertexOutput i) : COLOR {
    44.                 float4 _Base_var = tex2D(_Base,TRANSFORM_TEX(i.uv0, _Base));
    45.                 clip(_Base_var.a - 0.01);
    46. ////// Lighting:
    47. ////// Emissive:
    48.                 float3 emissive = _Base_var.rgb;
    49.                 float3 finalColor = emissive;
    50.                 return fixed4(finalColor,1);
    51.             }
    52.             ENDCG
    53.         }
    54.         Pass {
    55.             Name "ShadowCaster"
    56.             Tags {
    57.                 "LightMode"="ShadowCaster"
    58.             }
    59.             Offset 1, 1
    60.          
    61.             CGPROGRAM
    62.             #pragma vertex vert
    63.             #pragma fragment frag
    64.             #define UNITY_PASS_SHADOWCASTER
    65.             #include "UnityCG.cginc"
    66.             #include "Lighting.cginc"
    67.             #pragma fragmentoption ARB_precision_hint_fastest
    68.             #pragma multi_compile_shadowcaster
    69.             #pragma exclude_renderers gles gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2
    70.             #pragma target 3.0
    71.             uniform sampler2D _Base; uniform float4 _Base_ST;
    72.             struct VertexInput {
    73.                 float4 vertex : POSITION;
    74.                 float2 texcoord0 : TEXCOORD0;
    75.             };
    76.             struct VertexOutput {
    77.                 V2F_SHADOW_CASTER;
    78.                 float2 uv0 : TEXCOORD1;
    79.             };
    80.             VertexOutput vert (VertexInput v) {
    81.                 VertexOutput o = (VertexOutput)0;
    82.                 o.uv0 = v.texcoord0;
    83.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
    84.                 TRANSFER_SHADOW_CASTER(o)
    85.                 return o;
    86.             }
    87.             float4 frag(VertexOutput i) : COLOR {
    88.                 float4 _Base_var = tex2D(_Base,TRANSFORM_TEX(i.uv0, _Base));
    89.                 clip(_Base_var.a - 0.5);
    90.                 SHADOW_CASTER_FRAGMENT(i)
    91.             }
    92.             ENDCG
    93.         }
    94.     }
    95.     FallBack "Diffuse"
    96.     CustomEditor "ShaderForgeMaterialInspector"
    97. }
    98.  
    Thanks for directing me to your own solution for the shadow problem. I'm rather new to writing shader code manually (I usually use Shader Forge to make most of my custom shaders) so could you explain in a little more detail how I should use the example shader code in that thread? I'm guessing there is more to it than just copying and pasting the code into my project?
     
    Last edited: May 8, 2016
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    If you don't understand how to use that code I recommend you not use it.

    As for why it still doesn't work I missed the fact the forward pass is returning 1 for the alpha. It needs to use the alpha value of the texture for the alpha of return fixed4().
     
    brisck1 likes this.
  10. brisck1

    brisck1

    Joined:
    Jan 3, 2016
    Posts:
    9
    Awesome! So I changed

    return fixed4(finalColor,1);

    to

    return fixed4(finalColor, _Base_var.a);

    and that fixed it! Thanks so much for your help, I really hope Unity implement proper support for this with shadows soon.
     
  11. Pangamini

    Pangamini

    Joined:
    Aug 8, 2012
    Posts:
    54
    You can actually use the AlphaToMask with surface shaders without editing the compiled one, just add keepalpha to your surface shader #pragma
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    That works, as long as you don't need custom shadow casting, yes. The problem there actually comes from the fact that the shadow caster's fragment shader always outputs a zero, thus alpha to coverage is clipping the depth write. Using a shadow caster from a Fallback should work though.
     
  13. tjholleran

    tjholleran

    Joined:
    Apr 10, 2018
    Posts:
    7
    Thanks for this tread. Has anything happened in unity 2018.x that makes the shadow workarounds easier for alpha to cover?
     
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Nope.

    My solution these days has been to write a custom vertex fragment shadow pass in the shader instead of relying on the addshadow or fallback.
     
  15. tjholleran

    tjholleran

    Joined:
    Apr 10, 2018
    Posts:
    7
    Hola bgolus,
    I have followed down the rabbit hole, with basic custom shadow pass, and applied shadow in forward base.
    Shadows received on the surface look ok, but, shadows cast don't respect our alpha .... and though it casts an essentially opaque shadow, I con't see that thru the transparent portions of our alpha test gag.

    Is there s special version of SHADOW_CASTER_FRAGMENT(i) that I should be calling to respect the alpha, ... or do I need to set up samplers for the alpha texture in this pass?
    Pass
    {
    Tags{ "LightMode" = "ShadowCaster" }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #pragma multi_compile_shadowcaster
    #include "UnityCG.cginc"
    struct v2f {
    V2F_SHADOW_CASTER;
    };
    v2f vert(appdata_base v)
    {
    v2f o;
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    return o;
    }
    float4 frag(v2f i) : SV_Target
    {
    SHADOW_CASTER_FRAGMENT(i)
    }
    ENDCG
    }

    TJTest_AlphaToCover.png
    Here you can see a quad with the Naive :p version of the Alpha to Cover, ... and you can see geo thru the clipped alpha, but not the shadow. Yet, you can see the shadow of the entire quad.


    Thanks in advance,
    TJ
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    The shadow caster still needs to use traditional alpha testing.
     
  17. tjholleran

    tjholleran

    Joined:
    Apr 10, 2018
    Posts:
    7
    Thank you much ... I am in the ballpark I think ... have the custom textured shadow pass in.
    I ran into an interesting issue. My main pass mults the texture alpha with a facing ratio alpha.
    I don't want the facing fade to impact the shadows as I move the view, so I exclude any facing gag from the shadow pass. But, I shadows give a 'ghost' version of the textured alpha card in areas where the facing ratio nixes the main pass. This is a test not using the alphaToCover ... just so I can get the facing gag in.
    TJTest_AlphaToCover2.png

    Pass
    {
    Tags{ "LightMode" = "ShadowCaster" }

    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #pragma multi_compile_shadowcaster
    #include "UnityCG.cginc"
    #include "UnityStandardShadow.cginc"

    half _AlphaTextureBias;
    half _AlphaTextureBiasShadowOffset;

    struct appdata
    {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    float4 color : COLOR;

    UNITY_VERTEX_INPUT_INSTANCE_ID
    };


    struct v2f {
    V2F_SHADOW_CASTER;
    float2 uv : TEXCOORD0;
    };


    //v2f vert(appdata_base v)
    v2f vert(appdata v)
    {
    v2f o;
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    return o;
    }


    float4 frag(v2f i, fixed facing : VFACE) : SV_Target
    {
    half alpha = tex2D(_MainTex, i.uv.xy).a;
    alpha += _AlphaTextureBias + _AlphaTextureBiasShadowOffset;

    clip(alpha - _Cutoff);

    SHADOW_CASTER_FRAGMENT(i)
    }
    ENDCG
    }
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    The shadow caster pass is used for both shadow casting and the main directional light's shadow receiving. If you're fading out based on view direction in the main pass, you will need to also fade out in the shadow caster pass, but only when rendering the main scene depth.

    Unfortunately there's no clean way to do that. The best solution I have thus far found is to check to see if the current projection matrix is a perspective matrix or orthographic matrix.
    https://forum.unity.com/threads/dif...dow-caster-and-shadow-receiver-in-5-2.362653/
     
  19. tjholleran

    tjholleran

    Joined:
    Apr 10, 2018
    Posts:
    7
    Awesome, ... that did the trick.
    I took unity_LightShadowBias.z as the hook you were left with as the best, from that thread.

    Would not have found that for years ... so, thank u much for all the awesome info on this topic.
    Now, I am on to tackle some less naive version of my alphaTocCover.
     
  20. tjholleran

    tjholleran

    Joined:
    Apr 10, 2018
    Posts:
    7
    Back at this again.
    Am I correct in my understanding that sampling an individual cascade (furthest) is not easily achieved, ... and that SHADOW_ATTENUATION() is effectively a screen space shadow result of combining the cascades?

    Thanks in advance.
     
  21. atomicjoe

    atomicjoe

    Joined:
    Apr 10, 2013
    Posts:
    1,869