{"id":1554,"date":"2020-03-30T20:37:54","date_gmt":"2020-03-30T12:37:54","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=1554"},"modified":"2020-08-27T11:34:10","modified_gmt":"2020-08-27T03:34:10","slug":"unityanti-aliased-alpha-test-the-esoteric-alpha-to-coverage2","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=1554","title":{"rendered":"[Unity]Anti-aliased Alpha Test: The Esoteric Alpha To Coverage[2]"},"content":{"rendered":"\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"780\" height=\"220\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2020\/03\/a1.gif\" alt=\"\" class=\"wp-image-1555\"\/><\/figure>\n\n\n\n<p>We can approximate the loss of density from mip mapping almost perfectly with a&nbsp;<code>_MipScale<\/code>&nbsp;of&nbsp;<code>0.25<\/code>. The&nbsp;<code>CalcMipLevel()<\/code>&nbsp;function is the magic here. You can see the implementation of the function in the shader code below. Note if you use any kind of LOD bias on your textures you\u2019ll have to account for this manually. If you want more information about the function you can read this Stack Overflow post:<\/p>\n\n\n\n<p><a href=\"https:\/\/stackoverflow.com\/questions\/24388346\/how-to-access-automatic-mipmap-level-in-glsl-fragment-shader-texture\">https:\/\/stackoverflow.com\/questions\/24388346\/how-to-access-automatic-mipmap-level-in-glsl-fragment-shader-texture<\/a><\/p>\n\n\n\n<p>The same scaling trick works for alpha test as well. In fact if MSAA is disabled Alpha to Coverage falls back to alpha test. This means it isn\u2019t only useful for VR or other games where you\u2019re guaranteed to have MSAA enabled. Basically there\u2019s rarely a reason to use straight alpha testing ever!<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"4400\">Conclusion<\/h1>\n\n\n\n<p>You don\u2019t need to live with the aliasing of alpha test, the sorting issues of alpha blend, or the cost of a two pass shader. There is a happy middle ground using Alpha to Coverage. If you\u2019re using MSAA you should never need to use alpha test again!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"46cd\">Appendix<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"575f\">The Shader<\/h2>\n\n\n\n<p>The Unity ShaderLab file for the final shader used in this article can be found below.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Shader \"Custom\/Alpha To Coverage\"\n{\n    Properties\n    {\n        _MainTex (\"Texture\", 2D) = \"white\" {}\n        _Cutoff (\"Alpha cutoff\", Range(0.15,0.85)) = 0.4\n        _MipScale (\"Mip Level Alpha Scale\", Range(0,1)) = 0.25\n    }\n    SubShader\n    {\n        Tags { \"RenderQueue\"=\"AlphaTest\" \"RenderType\"=\"TransparentCutout\" }\n        Cull Off\n        \n        Pass\n        {\n            Tags { \"LightMode\"=\"ForwardBase\" }\n            AlphaToMask On\n            \n            CGPROGRAM\n            #pragma vertex vert\n            #pragma fragment frag\n            \n            #include \"UnityCG.cginc\"\n            #include \"Lighting.cginc\"\n            \n            struct appdata\n            {\n                float4 vertex : POSITION;\n                float2 uv : TEXCOORD0;\n                half3 normal : NORMAL;\n            };\n            \n            struct v2f\n            {\n                float4 pos : SV_POSITION;\n                float2 uv : TEXCOORD0;\n                half3 worldNormal : NORMAL;\n            };\n            \n            sampler2D _MainTex;\n            float4 _MainTex_ST;\n            float4 _MainTex_TexelSize;\n            \n            fixed _Cutoff;\n            half _MipScale;\n            \n            float CalcMipLevel(float2 texture_coord)\n            {\n                float2 dx = ddx(texture_coord);\n                float2 dy = ddy(texture_coord);\n                float delta_max_sqr = max(dot(dx, dx), dot(dy, dy));\n                \n                return max(0.0, 0.5 * log2(delta_max_sqr));\n            }\n            \n            v2f vert (appdata v)\n            {\n                v2f o;\n                o.pos = UnityObjectToClipPos(v.vertex);\n                o.uv = TRANSFORM_TEX(v.uv, _MainTex);\n                o.worldNormal = UnityObjectToWorldNormal(v.normal);\n                return o;\n            }\n            \n            fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target\n            {\n                fixed4 col = tex2D(_MainTex, i.uv);\n                \/\/ rescale alpha by mip level (if not using preserved coverage mip maps)\n                col.a *= 1 + max(0, CalcMipLevel(i.uv * _MainTex_TexelSize.zw)) * _MipScale;\n                \/\/ rescale alpha by partial derivative\n                col.a = (col.a - _Cutoff) \/ max(fwidth(col.a), 0.0001) + 0.5;\n                \n                half3 worldNormal = normalize(i.worldNormal * facing);\n                \n                fixed ndotl = saturate(dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));\n                fixed3 lighting = ndotl * _LightColor0;\n                lighting += ShadeSH9(half4(worldNormal, 1.0));\n                \n                col.rgb *= lighting;\n                \n                return col;\n            }\n            ENDCG\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"560d\">Unity\u2019s Surface Shaders &amp; Directional Shadows<\/h2>\n\n\n\n<p>Many peoples\u2019 first inclination is probably going to be to try this technique in Unity with a surface shader by adding&nbsp;<code>AlphaToMask On<\/code>. This does work, but it breaks shadow casting and receiving if you use the&nbsp;<code>addshadow<\/code>&nbsp;surface option due to Unity\u2019s default shadow caster passes outputing 0% alpha and&nbsp;<code>AlphaToMask On<\/code>&nbsp;still being enabled for the generated pass. You can use&nbsp;<code>Fallback \u201cLegacy Shaders\/Transparent\/Cutout\/VertexLit\u201d<\/code>&nbsp;instead as fallback passes don\u2019t inherent the parent shader file\u2019s render settings, but it won\u2019t do the mip level alpha rescaling so it might look odd in the distance unless you use a custom shadow caster pass. Even then Unity\u2019s directional shadows will sometimes cause bright and dark noise around the edges or seemingly disable anti-aliasing since their directional shadows are rendered with out MSAA. This is one of the reasons why MSAA never seems to work as well as you might expect it to in Unity. You can disable shadow cascades in the Graphics settings, but then the shadows don\u2019t look as nice. It does mean if you\u2019re not using real time directional shadows on the PC or consoles, or you\u2019re working on a mobile project, this technique works great. Upcoming changes to Unity\u2019s forward renderer will also likely solve this.<\/p>\n\n\n\n<p>There is a work around for getting shadow cascades and MSAA working together better, which I used for&nbsp;<a rel=\"noreferrer noopener\" href=\"http:\/\/www.uberent.com\/waywardsky\/\" target=\"_blank\">Wayward Sky<\/a>&nbsp;and&nbsp;<a rel=\"noreferrer noopener\" href=\"http:\/\/www.uberent.com\/dinofrontier\/\" target=\"_blank\">Dino Frontier<\/a>&nbsp;(both out now for the PSVR). But that is for another article. I posted the basic idea on the Unity forums&nbsp;<a rel=\"noreferrer noopener\" href=\"https:\/\/forum.unity3d.com\/threads\/fixing-screen-space-directional-shadows-and-anti-aliasing.379902\/\" target=\"_blank\">here<\/a>&nbsp;for those curious.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"480\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2020\/03\/a2.png\" alt=\"\" class=\"wp-image-1556\" srcset=\"https:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2020\/03\/a2.png 640w, https:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2020\/03\/a2-300x225.png 300w, https:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2020\/03\/a2-67x50.png 67w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/figure>\n\n\n\n<p> artifacts and apparent loss of anti-aliasing from Unity\u2019s cascaded shadow maps (left); cascades disabled in Graphics settings (center); cascades and MSAA with fix (right) <\/p>\n\n\n\n<p>\u00b9<a href=\"https:\/\/medium.com\/@bgolus\/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f#5de2\">^<\/a>Rendering an object once using an alpha test pass, and again with an alpha blend pass is a good solution, especially if you don\u2019t have MSAA. There are plenty of places that have discussed this technique, like Wolfire Games\u2019 blog&nbsp;<a href=\"http:\/\/blog.wolfire.com\/2009\/02\/rendering-plants-with-smooth-edges\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>&nbsp;(which also mentions an Alpha to Coverage like technique) and even Unity\u2019s documentation on&nbsp;<a href=\"https:\/\/docs.unity3d.com\/Manual\/SL-AlphaTest.html\" target=\"_blank\" rel=\"noreferrer noopener\">Legacy Alpha Testing<\/a>. Interestingly, Alpha to Coverage can be used leverage the two pass technique for softer blending than is normally possible with Alpha to Coverage alone, so effectively getting the benefits of Alpha to Coverage and alpha blending. It\u2019s mentioned briefly on Luis Antonio\u2019s site in a post about the clouds in The Witness. It\u2019s actually a section written by Ignacio Casta\u00f1o again.<\/p>\n\n\n\n<figure class=\"wp-block-embed-wordpress wp-block-embed is-type-wp-embed is-provider-art-of-luis\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"0jJtI1KP3P\"><a href=\"http:\/\/www.artofluis.com\/3d-work\/the-art-of-the-witness\/clouds\/\">Clouds<\/a><\/blockquote><iframe title=\"&#8220;Clouds&#8221; &#8212; Art of Luis\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\" src=\"http:\/\/www.artofluis.com\/3d-work\/the-art-of-the-witness\/clouds\/embed\/#?secret=0jJtI1KP3P\" data-secret=\"0jJtI1KP3P\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\u00b2<a href=\"https:\/\/medium.com\/@bgolus\/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f#dec4\">^<\/a>Two coverage samples result in something similar to 50% opaque, but only if using gamma (aka sRGB) color space rendering. Otherwise if you\u2019re using linear color space rendering and not using a custom resolver it\u2019s more like ~74% alpha, or ~188\/255, which if you\u2019re familiar with linear to gamma conversions might look familiar.<\/p>\n\n\n\n<p>\u00b3<a href=\"https:\/\/medium.com\/@bgolus\/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f#41e8\">^<\/a>Pixel derivatives work in blocks of 2&#215;2 pixels, so&nbsp;<code>fwidth()<\/code>&nbsp;might be comparing the pixel above or below, and the pixel to the left or right, depending on which pixel in the 2&#215;2 block the pixel\/fragment shader is currently running. The&nbsp;<code>ddx()<\/code>&nbsp;and&nbsp;<code>ddy()<\/code>&nbsp;functions individually give the horizontal and vertical partial derivatives. These are always compared in the same order, so the&nbsp;<code>ddx()<\/code>&nbsp;and&nbsp;<code>ddy()<\/code>&nbsp;values for each pair of pixels is the same. Depending on the implementation it may even be the same values for all four pixels in the 2&#215;2 grid. Basically 4 instances of a pixel\/fragment shader is running in parallel always, even if a triangle is only covering one of those pixels.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We can approximate the loss of density from mip mapping [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[18],"_links":{"self":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/1554"}],"collection":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1554"}],"version-history":[{"count":2,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/1554\/revisions"}],"predecessor-version":[{"id":2240,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/1554\/revisions\/2240"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1554"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1554"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1554"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}