Need to improve 2D circle shader performance

Hi, I’m working on a mobile game. I need resizeable rings (not stroke size but radius). I’ve written a shader works great but shows poor performance on mobile phones.

I set shader’s float array(RadiusArray) from outside once in a while to add more circles. However whenever I add new circle, it gets slower and slower until I remove all circles from the array. Array has two information about a circle: Radius and Opacity.

Is there any way that I don’t have to use for-loop like matrix multiplication etc.? Or any tips for optimization?

Thank you.

Here is how it looks;
89217-capture.jpg

Shader "Custom/CircleShader" {
    Properties{
        _Color("Color", Color) = (1,1,0,0)
        _Thickness("Thickness", Range(0.0,0.5)) = 0.05
        _Radius("Radius", Range(0.0, 0.5)) = 0.4
    }
    SubShader{
    //Tags{ "Queue" = "Transparent"  "RenderType" = "Transparent" "IgnoreProjector" = "True" }
    Pass{
    Blend SrcAlpha OneMinusSrcAlpha // Alpha blending
 
 
    //Cull Off
    //Lighting Off
    //ZWrite Off //Make On for background
    //Fog{ Mode Off }
 
    CGPROGRAM
 
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
 
 
    fixed4 _Color; // low precision type is usually enough for colors
    fixed _Thickness;
    fixed _Radius;
    fixed RadiusArray[80]; //Coming from outside
 
    struct appdata_t
    {
        float4 vertex   : POSITION;
        float2 texcoord : TEXCOORD0;
    };
    struct fragmentInput {
        fixed4 pos : POSITION;
        fixed2 uv : TEXCOORD0;
    };
    fragmentInput vert(appdata_t v)
    {
        fragmentInput o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.texcoord.xy - fixed2(0.5,0.5);
        return o;
    }
 
    // r = radius
    // d = distance
    // t = thickness
   
    fixed antialias(fixed d, fixed t) {
        fixed innerRadius = 0;
        fixed outerRadius = 0;
        fixed totalPixelOpacity = 0;
        fixed circleOpacity = 0.0;
        fixed currentcircleOpacity = 0.0;
 
        fixed radius;
        int circleCount = (int)RadiusArray[0];
 
        for (int ri = circleCount * 2; ri > 0; ri -= 2)
        {
            radius = RadiusArray[ri - 1];
            if (radius < 0 || totalPixelOpacity >= 1) //Last item
                break;
 
            innerRadius = radius - 0.5*t;
            outerRadius = radius + 0.5*t;
            circleOpacity = RadiusArray[ri];
 
            currentcircleOpacity =
                1 - (1 - (smoothstep(innerRadius - 0.003,
                    innerRadius,
                    d))
 
                    + (smoothstep(outerRadius,
                        outerRadius + 0.003,
                        d)));
 
            currentcircleOpacity *= circleOpacity;
            totalPixelOpacity += currentcircleOpacity;
        }
 
        return totalPixelOpacity > 1 ? 1 : totalPixelOpacity;
 
    }
 
 
    fixed4 frag(fragmentInput i) : SV_Target{
 
 
    fixed distance = 0;
        distance = length(fixed2(i.uv.x, i.uv.y));
        return fixed4(_Color.r, _Color.g, _Color.b, _Color.a*antialias(distance, _Thickness));
    }
 
 
        ENDCG
    }
    }
}

Rather than specify the circles, and compute the colors , inside the shader, I would recommend you pass a texture, 1 pixel wide, to the shader instead.

You can compute and generate (and assign to the shader) this texture ONCE, only when you CHANGE the number of circles, their radius’, or thicknesses, using a regular c# class. In this class you would loop though all the possible “distance” values, and assign the resultant color to the 1 pixel wide texture. The color would be stored at the uv.y coordinate equal to the distance.

Then, your fragment shader will only need to lookup the pixel color (via UV) of this texture based upon your fragment-shader-computed “distance” value.

So, if your texture was say 1 pixel wide (in uv.X direction), you could use this to get the pixel color in your fragment shader:

distance = length(fixed2(i.uv.x, i.uv.y));
fixed4 color = tex2D(_MainTex, float2(0,distance));

Alternatively, you can use this method (compute texture outside of shader, only on changes) to generate a FULL texture (not one pixel), and just assign it to a standard shader.