Search code examples
unity-game-engineshader

Unity - 2D circle hole with antialiasing on object


I'd like to add a hole inside an empty object with sprites inside. This hole has a circle shape.

To achieve that, I added a shader to a material and the material to a Sprite Renderer component to the empty object.

Here is my Shader :

Shader "Sprite/Hole Effect"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _ObjPos ("Object Position", Vector) = (1,1,1,1)
        _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
        _Radius ("Hole Radius", Range(0.1,10)) = 2
    }
 
    SubShader
    {
        Pass
        {
            Cull Off
 
            CGPROGRAM
 
            #pragma vertex vert
            #pragma fragment frag
 
            uniform sampler2D _MainTex;
            float _Radius;
            float4 _ObjPos;
 
            struct vertexInput {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };
            struct vertexOutput {
                float4 pos : SV_POSITION;
                float4 worldPos : POSITION1;
                float4 tex : TEXCOORD0;
            };
 
            vertexOutput vert(vertexInput input)
            {
                vertexOutput output;
 
                output.tex = input.texcoord;
                output.pos = UnityObjectToClipPos(input.vertex);
                output.worldPos = mul(input.vertex, unity_ObjectToWorld);
                return output;
            }
 
            float4 frag(vertexOutput input) : COLOR
            {
                float4 textureColor = tex2D(_MainTex, input.tex.xy);
                float dis = distance(input.worldPos.xyz, _ObjPos);
 
                if (dis > _Radius)
                {
                    discard;
                }
 
                return textureColor;
            }
 
            ENDCG
        }
    }
 
    FallBack "Diffuse"
}

The problem with this method is that there is no antialiasing for the circle...

So I tried to add a Sprite mask to the empty object instead, but there is no antialiasing nether...

I think the shader is the good solution to achieve that but really don't know how!

Any help would be perfect!!!


Solution

  • Finally I found it!!

    I applied a material with a shader to the sprites of the object where I want to see the circle.

    Here is my shader:

    Shader "Sprites/XRay Effect"
    {
        Properties
        {
            [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
            
            _PlayerPos ("Player Position", Vector) = (0,0,0,0)
            _Radius ("Hole Radius", Range(0.1,100)) = 2
            _Border ("Border", Range(0.001,1)) = 0.05
        }
    
        SubShader
        {
            Tags
            { 
                "Queue"="Transparent" 
                "IgnoreProjector"="True" 
                "RenderType"="Transparent" 
                "PreviewType"="Plane"
                "CanUseSpriteAtlas"="True"
            }
            
            Cull Off
            Lighting Off
            ZWrite Off
            ZTest [unity_GUIZTestMode]
            Blend SrcAlpha OneMinusSrcAlpha
    
            Pass
            {
                CGPROGRAM
    
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
                #include "UnityUI.cginc"
                
                struct vertexInput
                {
                    float4 vertex   : POSITION;
                    float4 color    : COLOR;
                    float2 texCoord : TEXCOORD0;
                };
    
                struct vertexOutput
                {
                    float4 vertex   : SV_POSITION;
                    fixed4 color    : COLOR;
                    half2 texCoord  : TEXCOORD0;
                    float3 worldPosition : TEXCOORD1;
                };
                
                sampler2D _MainTex;
                
                float4 _PlayerPos;
                float _Radius;
                float _Border;
    
                vertexOutput vert(vertexInput IN)
                {
                    vertexOutput OUT;
    
                    OUT.vertex = UnityObjectToClipPos(IN.vertex);
                    OUT.texCoord = IN.texCoord;
                    OUT.worldPosition = mul(unity_ObjectToWorld, IN.vertex);
                    OUT.color = IN.color;
    
                    return OUT;
                }
    
                fixed4 frag(vertexOutput IN) : SV_Target
                {
                    half4 color = tex2D(_MainTex, IN.texCoord) * IN.color;
                    
                    // Adding the circle hole
                    float dis = distance(IN.worldPosition.xyz, _PlayerPos.xyz);
                    color.a = lerp(0, color.a, color.a * (dis - _Radius) / _Border);
    
                    return color;
                }
                
                ENDCG
            }
        }
        FallBack "UI/Default"
    }
    

    You can modify the part lerp(0, color.a, color.a * (dis - _Radius) / _Border) to your needs.