How to draw circle in unity by shader and anti-aliasing

13,965

Solution 1

It's really easy to apply anti-alias to a circle.

1.First, you need 3 variables to do this. Get the radius, distance of the circle. Also create a float value(let's called that borderSize) that can be used to determine how far the anti-alias should go. The radius, distance and borderSize are the three variables.

2.Find the t with smoothstep function using those 3 variables from #1.

float t = smoothstep(radius + borderSize, radius - borderSize, distance); 

3.Mix the color before returning it.

Let's say that _BoundColor is the circle fill color and _BgColor is the background color.

If using GLSL using the mix function. If using Unity, use the lerp function. Both function are interchanging and the parameters are the-same.

col = lerp(_BoundColor, _BgColor, t);

The t is from #2. You can now return col in the fragment function.


These are the 3 steps put together:

if (dis > radius) {
    float t = smoothstep(radius + borderSize, radius - borderSize, distance);
    col = lerp(_BoundColor, _BgColor, t); 
}
else {
    float t = smoothstep(radius + borderSize, radius - borderSize, distance);
    col = lerp(_BoundColor, _BgColor, t);
}
return col;

OUTPUT WITHOUT ANTI-ALIASING:

enter image description here

OUTPUT WITH ANTI-ALIASING(4.5 Threshold):

enter image description here


Finally, the whole code(Tested on PC and Android but should work on iOS too)

Shader "Unlit/Circle Anti-Aliasing"
{
    Properties
    {
        _BoundColor("Bound Color", Color) = (0,0.5843137254901961,1,1)
        _BgColor("Background Color", Color) = (0.1176470588235294,0,0.5882352941176471,1)
        _circleSizePercent("Circle Size Percent", Range(0, 100)) = 50
        _border("Anti Alias Border Threshold", Range(0.00001, 5)) = 0.01
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            float _border;

            fixed4 _BoundColor;
            fixed4 _BgColor;
            float _circleSizePercent;

            struct v2f
            {
                float2 uv : TEXCOORD0;
            };

            v2f vert(
                float4 vertex : POSITION, // vertex position input
                float2 uv : TEXCOORD0, // texture coordinate input
                out float4 outpos : SV_POSITION // clip space position output
            )
            {
                v2f o;
                o.uv = uv;
                outpos = UnityObjectToClipPos(vertex);
                return o;
            }

            float2 antialias(float radius, float borderSize, float dist)
            {
                float t = smoothstep(radius + borderSize, radius - borderSize, dist);
                return t;
            }

            fixed4 frag(v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
            {
                float4 col;
                float2 center = _ScreenParams.xy / 2;

                float maxradius = length(center);

                float radius = maxradius*(_circleSizePercent / 100);

                float dis = distance(screenPos.xy, center);

                if (dis > radius) {
                    float aliasVal = antialias(radius, _border, dis);
                    col = lerp(_BoundColor, _BgColor, aliasVal); //NOT needed but incluse just incase
                }
                else {
                    float aliasVal = antialias(radius, _border, dis);
                    col = lerp(_BoundColor, _BgColor, aliasVal);
                }
                return col;

            }
            ENDCG
        }
    }
}

Solution 2

Try this:

Shader "Unlit/CircleSeletor"
{
    Properties
    {
        _BoundColor("Bound Color", Color) = (1,1,1,1)
        _BgColor("Background Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BoundWidth("BoundWidth", float) = 10
        _ComponentWidth("ComponentWidth", float) = 100
    }

    SubShader
    {
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag Lambert alpha
            // make fog work
            #pragma multi_compile_fog
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float _BoundWidth;
            fixed4 _BoundColor;
            fixed4 _BgColor;
            float _ComponentWidth;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                    float4 vertex : SV_POSITION;
            };

            float4 _MainTex_ST;
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            float antialias(float w, float d, float r) {
                return 1 - (d - r - w / 2) / (2 * w);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 c = tex2D(_MainTex,i.uv);
            float x = i.uv.x;
            float y = i.uv.y;
            float dis = sqrt(pow((0.5 - x), 2) + pow((0.5 - y), 2));
            if (dis > 0.5) {
                c.a = 0;
                discard;
            }
            else {
                float innerRadius = (_ComponentWidth * 0.5 - _BoundWidth) / _ComponentWidth;
                if (dis > innerRadius) {
                    c = _BoundColor;
                    //c.a = c.a*antialias(_BoundWidth, dis, innerRadius);
                }
                else {
                    c = _BgColor;
                }
            }
            return c;
            }

                ENDCG
        }
        GrabPass{
            "_MainTex2"
        }
        Pass
        {
                Blend One zero
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
            };
            struct v2f 
            {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR;
                float4 scrPos : TEXCOORD0;
            };

            float4 _MainTex_ST;
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);    
                o.scrPos = ComputeScreenPos(o.pos);
                o.color = v.color;
                return o;
            }

            sampler2D _MainTex2;
            float4 _MainTex2_TexelSize;

            fixed4 frag(v2f i) : SV_Target
            {
                float2 uv = (i.scrPos.xy / i.scrPos.w);
                fixed4 c = tex2D(_MainTex2, uv );
                fixed4 up = tex2D(_MainTex2, uv + fixed2(0, _MainTex2_TexelSize.y));
                fixed4 down = tex2D(_MainTex2, uv - fixed2(0, _MainTex2_TexelSize.y));
                fixed4 left = tex2D(_MainTex2, uv - fixed2(_MainTex2_TexelSize.x, 0));
                fixed4 right = tex2D(_MainTex2, uv + fixed2(_MainTex2_TexelSize.x, 0));

                c.rgb = (c.rgb + up.rgb + down.rgb + left.rgb + right.rgb) / 5;
                c.a = (c.a + up.a + down.a + left.a + right.a) / 5;

                return c;
            }
                ENDCG

        }
    }
}

After first pass I GrabPass result and apply anti-alias in second pass by averaging border pixels.

Share:
13,965
user1419851
Author by

user1419851

Updated on June 05, 2022

Comments

  • user1419851
    user1419851 almost 2 years

    I have drawn a circle by shader, but I can't get anti-aliasing to work.

    I tried finding an answer here http://answers.unity3d.com/questions/521984/how-do-you-draw-2d-circles-and-primitives.html, but I have to use discard to draw circle.

    Here is a picture of my current shader result and the shader code:

    picture

    Shader "Unlit/CircleSeletor"
    {
    Properties
        {
            _BoundColor("Bound Color", Color) = (1,1,1,1)
            _BgColor("Background Color", Color) = (1,1,1,1)
            _MainTex("Albedo (RGB)", 2D) = "white" {}
            _BoundWidth("BoundWidth", float) = 10
            _ComponentWidth("ComponentWidth", float) = 100
        }
    SubShader{
    Pass
                {
                Blend SrcAlpha OneMinusSrcAlpha
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag Lambert alpha
                // make fog work
                #pragma multi_compile_fog
                #include "UnityCG.cginc"
                sampler2D _MainTex;
                float _BoundWidth;
                fixed4 _BoundColor;
                fixed4 _BgColor;
                float _ComponentWidth;
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    UNITY_FOG_COORDS(1)
                    float4 vertex : SV_POSITION;
                };
                float4 _MainTex_ST;
                v2f vert(appdata v)
                {
                    v2f o;
                    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    UNITY_TRANSFER_FOG(o,o.vertex);
                    return o;
                }
                float antialias(float w, float d, float r) {
                        return 1-(d-r-w/2)/(2*w);
                }
                fixed4 frag(v2f i) : SV_Target
                {
                    fixed4 c = tex2D(_MainTex,i.uv);
                    float x = i.uv.x;
                    float y = i.uv.y;
                    float dis = sqrt(pow((0.5 - x), 2) + pow((0.5 - y), 2));
                    if (dis > 0.5) {
                        discard;
                    } else {
                        float innerRadius = (_ComponentWidth * 0.5 - _BoundWidth) / _ComponentWidth;
                        if (dis > innerRadius) {
                            c = _BoundColor;
                            //c.a = c.a*antialias(_BoundWidth, dis, innerRadius);
                        }
                        else {
                            c = _BgColor;
                        }
                    }
                    return c;
                }
                ENDCG
                }
    }
    }
    
  • Fattie
    Fattie almost 7 years
    Ah - you know unfortunately I think this shader doesn't work on modern iOS devices - you get a "kaleidoscope" effect you know? Bad luck!
  • Krajca
    Krajca almost 7 years
    what do you mean? can you provide screenshot?
  • Fattie
    Fattie almost 7 years
    for sure @Krajca, I will do that. By all means, I may have stuffed something up ... stand by
  • Fattie
    Fattie almost 7 years
    absolutely not - just a disc with antialiasing ! thx !!
  • Fattie
    Fattie almost 7 years
    one thing that confuses me guys is, when you just click "2d Sprite", it has no .. "size". it's very abstract. there's no mesh there, no nothing. I think one problem I may have had with @Krajca's excellent shader is, I just went ... 2dSprite .. and then I set a material (which was, simply, Krajca's shader). I got the broken-boundaries effect I mention. I think in the shader it should "make" a size ..... or something. (A natural size would be "1 unit diameter".)
  • Wipster
    Wipster almost 6 years
    On what component do you attach the material holding this shader?
  • Programmer
    Programmer almost 6 years
    @Wipster I don't remember much but it's either a 3D Object (MeshRenderer) or* to a camera. I believe this was a screen shader attached to the main camera. Try both and let me know