1
\$\begingroup\$

I made a circle shader by following this tutorial: https://www.youtube.com/watch?v=Ww1GbfnBH_Q. It is simple flat circle shader. The problem is that there are visible jigged lines on the circle edges. Here is how it looks: enter image description here

And here is shader code:

  Shader "Custom/CircleShader"
{
   Properties {
        _ForegroundColor("Foreground Color", Color) = (1,1,1,1)
        _BackgroundColor("Background Color", Color) = (0,0,0,1)
        _ForegroundMask ("Foreground Mask", 2D) = "white" {}
        _ForegroundCutoff ("Foreground Cutoff", Range(0,1)) = 0.5
        _BackgroundCutoff("Background Cutoff", Range(0,1)) = 0.5
    }
    SubShader {
        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 200
        
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows keepalpha

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _ForegroundMask;

        struct Input {
            float2 uv_ForegroundMask;
        };

        fixed4 _ForegroundColor;
        fixed4 _BackgroundColor;
        half _ForegroundCutoff;
        half _BackgroundCutoff;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            fixed x = (-0.5 + IN.uv_ForegroundMask.x) * 2;
            fixed y = (-0.5 + IN.uv_ForegroundMask.y) * 2;

            // Albedo comes from a texture tinted by color
            fixed radius = 1 - sqrt(x*x + y*y);
            clip(radius - _BackgroundCutoff);
            o.Albedo = _BackgroundColor;
            if (radius > _ForegroundCutoff) {
                o.Albedo = _ForegroundColor;
            }
            o.Alpha = 1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

How would I add antialiasing to the shader to remove jigged lines on edges of the circle?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Do you mean the aliasing (aka jaggies)? Usual solution is some manner of anti-aliasing. In this case there are circle specific algorithms for anti-aliased circles. \$\endgroup\$
    – Pikalek
    Feb 16, 2022 at 15:33
  • \$\begingroup\$ I edited the questions so it is more specific now. \$\endgroup\$
    – Ivan
    Feb 17, 2022 at 10:24

1 Answer 1

2
\$\begingroup\$

Right now your code outputs either foreground colour, or background colour, or nothing — no in-between values. That's what creates the harsh stair-step between the three levels.

To disguise the pixel grid rather than call attention to it, we need to output some pixels with intermediate colours — partway between foreground and background, or between background and transparent.

If you mostly see this graphic straight-on/at the same size, a simple lerp will do OK:

Shader "Custom/CircleShader"
{
   Properties {
        _ForegroundColor("Foreground Color", Color) = (1,1,1,1)
        _BackgroundColor("Background Color", Color) = (0,0,0,1)
        _ForegroundMask ("Foreground Mask", 2D) = "white" {}
        _ForegroundCutoff ("Foreground Cutoff", Range(0,1)) = 0.5
        _BackgroundCutoff("Background Cutoff", Range(0,1)) = 0.5
        _Sharpness("Sharpness", float) = 20.0
    }
    SubShader {
        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 200
        
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows keepalpha alpha:fade

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _ForegroundMask;

        struct Input {
            float2 uv_ForegroundMask;
        };

        fixed4 _ForegroundColor;
        fixed4 _BackgroundColor;
        half _ForegroundCutoff;
        half _BackgroundCutoff;
        half _Sharpness;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            fixed x = (-0.5 + IN.uv_ForegroundMask.x) * 2;
            fixed y = (-0.5 + IN.uv_ForegroundMask.y) * 2;

            // If you call a variable "radius", make it
            // actually the radius, not its complement!
            fixed radius = sqrt(x*x + y*y);

            float inside = _BackgroundCutoff - radius;
            clip(inside);
            // Feather the outer edge a little.
            o.Alpha = min(inside * _Sharpness, 1);

            // Feather the transition a little.
            float fore = saturate(_Sharpness * (_ForegroundCutoff - radius));
            o.Albedo = lerp(_BackgroundColor, _ForegroundColor, fore);
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Play with the "Sharpness" parameter to control how wide the blending region is.

You'll find though, if you need to show this graphic at different sizes/distances, or tilted, you want different degrees of sharpness at each: the bigger the object on screen, the more pixels it occupies, so the transition region can be smaller without disappearing between the pixel grid cells.

To dynamically adapt to the display scale, you can use screenspace derivatives to compute how many pixels you are from the transition threshold, and blend only the pixels immediately adjacent to the threshold line.

That technique is shown in past answers, so I won't repeat it here just now:

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .