Alpha compositing

Alpha compositing is the technique of combining an image with a background image to produce a composite image that has the appearance of transparency.

Alpha testing is a widely implemented form of alpha compositing. However, it can produce severe aliasing effects at the edges of objects as the alpha channel is bitwise, so there is no blending between edges.

Multisampling has no impact in this case as the shader is run only once for each pixel. Each subsample returns the same alpha value, leaving the edges aliased.

Alpha blending is an alternative solution. But, without polygonal sorting, the blending fails and objects are rendered incorrectly. However, enabling sorting is an expensive process and diminishes performance.

Alpha to Coverage (ATOC) is a different method of alpha compositing which can help reduce aliasing. ATOC transforms the alpha component output of the fragment shader into a coverage mask and combines this with the multisampling mask. ATOC then uses an AND operator, only rendering pixels that pass the operation.

Visual example demonstrating how alpha testing and alpha coverage looks

Both the image and the video at the following link demonstrates the difference between a basic alpha test implementation, which is on the left, and an alpha to coverage implementation, which is on the right:

https://developer.arm.com/graphics/videos/alpha-compositing-example

Implementing alpha compositing in Unity

Alpha to Coverage is implemented within Unity shaders.

Mip Maps Preserve Coverage option - Unity 2017 onwards

The Mip Maps Preserve Coverage option removes the need for the mip map level calculation that is required in earlier versions. It can be enabled in the Texture Inspector.

To enable ATOC in Unity from 2017 or later, you must first create a new shader in the Project window, then insert the following shader code:

Note: Apply this shader to the material and set that material to the object which requires alpha compositing.

AlphaToMask On
	struct frag_in
	{
	float4 pos : SV_POSITION;
	float2 uv : TEXCOORD0;
	half3 worldNormal : NORMAL;
	};
	
	fixed4 frag(frag_in i, fixed facing : VFACE) : SV_Target
	{
	/* Sample diffuse texture */
	fixed4 col = tex2D(_MainTex, i.uv);
	
	/* Sharpen texture alpha to the width of a pixel */
	col.a = (col.a - 0.5) / max(fwidth(col.a), 0.0001) + 0.5;
	clip(col.a - 0.5);
	
	/* Lighting calculations */
	half3 worldNormal = normalize(i.worldNormal * facing);
	fixed ndotl = saturate(dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));
	col.rgb *= ndotl * _LightColor0;
	return col;
	}

For versions of Unity before 2017

To enable ATOC in pre-2017 versions of Unity, you must also add a mipmap level calculation to your shader code. As with 2017 and newer versions of Unity, you must first create a new shader in the Project Window. Then insert the following shader code:

Note:  Apply this shader to the material and set that material to the object which requires alpha compositing.

Shader "Custom/Alpha To Coverage"
{
  Properties
  {
    MainTex("Texture", 2D) = "white" {}
  }
    SubShader
  {
    Tags { "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" } Cull Off
    Pass
    {
      Tags { "LightMode" = "ForwardBase" }
      CGPROGRAM
      #pragma vertex vert #pragma fragment frag

      #include "UnityCG.cginc"
      #include "Lighting.cginc"

      struct appdata
      {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
        half3 normal : NORMAL;
      };

      struct v2f
      {
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
        half3 worldNormal : NORMAL;
      };

      sampler2D _MainTex; float4 _MainTex_ST;
      float4 _MainTex_TexelSize;

      v2f vert(appdata v)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        o.worldNormal = UnityObjectToWorldNormal(v.normal);
        return o;
      }

      fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
      {
        fixed4 col = tex2D(_MainTex, i.uv);
        float2 texture_coord = i.uv * _MainTex_TexelSize.zw; float2 dx = ddx(texture_coord);
        float2 dy = ddy(texture_coord);
        float MipLevel = max(0.0, 0.5 * log2(max(dot(dx, dx), dot(dy, dy))));
        col.a *= 1 + max(0, MipLevel) * 0.25; clip(col.a - 0.5);
        half3 worldNormal = normalize(i.worldNormal * facing);
        fixed ndotl = saturate(dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));
        fixed3 lighting = ndotl * _LightColor0; lighting += ShadeSH9(half4(worldNormal, 1.0));
        col.rgb *= lighting; return col;
      }
      ENDCG
    }
  }
}
Previous Next