How do you draw 2D circles and primitives

I want to know how to draw clean, anti-aliased, scalable circles / ovals / lines and other primitives in Unity!

I know you can create a quad for a square, and you can create a mesh for polygons (like a star, or a pentagon, for example).

But a circle is unique - using a cylinder with 0 height for a circle doesn’t work as scaling it shows the polygons.

If anybody knows the game Hundreds on iOS - basically, how is that sort of thing attainable?

Cheers all!

Building off Walter’s answer, and adding some more parameters and simple-anti aliasing:

Shader "Custom/Circle" {
     Properties {
         _Color ("Color", Color) = (1,0,0,0)
		 _Thickness("Thickness", Range(0.0,0.5)) = 0.05
		 _Radius("Radius", Range(0.0, 0.5)) = 0.4
		 _Dropoff("Dropoff", Range(0.01, 4)) = 0.1
     }
     SubShader {
         Pass {
			 Blend SrcAlpha OneMinusSrcAlpha // Alpha blending
             CGPROGRAM
			
             #pragma vertex vert
             #pragma fragment frag
             #include "UnityCG.cginc"
			
			
             fixed4 _Color; // low precision type is usually enough for colors
			float _Thickness;
			float _Radius;
			float _Dropoff;
			
             struct fragmentInput {
                 float4 pos : SV_POSITION;
                 float2 uv : TEXTCOORD0;
             };
 
             fragmentInput vert (appdata_base 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
			 // p = % thickness used for dropoff
			 float antialias(float r, float d, float t, float p) {
				 if( d < (r - 0.5*t))
					return - pow( d - r + 0.5*t,2)/ pow(p*t, 2) + 1.0;
				else if ( d > (r + 0.5*t))
					return - pow( d - r - 0.5*t,2)/ pow(p*t, 2) + 1.0; 
				else
					return 1.0;
			 }
			 
             fixed4 frag(fragmentInput i) : SV_Target {
                float distance = sqrt(pow(i.uv.x, 2) + pow(i.uv.y,2));
					
                return fixed4(_Color.r, _Color.g, _Color.b, _Color.a*antialias(_Radius, distance, _Thickness, _Dropoff));
             }
			 
			 
             ENDCG
         }
     }
 }

You can create meshes using the Mesh class. For a circle, use many polygons so the straight lines aren’t distinguishable.

I know this post is years old, but for future visitors;
There is an alternative to creating a ‘perfect’ circle that will be perfect at any size, using a custom vertex/fragment shader. Here is my current implementation:

Shader "Custom/SolidColor" {
	Properties {
		_Color ("Color", Color) = (1,0,0,0)
	}
	SubShader {
		Pass {
			CGPROGRAM

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

			fixed4 _Color; // low precision type is usually enough for colors

			struct fragmentInput {
				float4 pos : SV_POSITION;
				float2 uv : TEXTCOORD0;
			};

			fragmentInput vert (appdata_base v)
			{
				fragmentInput o;

				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.texcoord.xy - fixed2(0.5,0.5);

				return o;
			}

			fixed4 frag(fragmentInput i) : SV_Target {
				float distance = sqrt(pow(i.uv.x, 2) + pow(i.uv.y,2));
				//float distancez = sqrt(distance * distance + i.l.z * i.l.z);


				if(distance > 0.5f){
					return fixed4(1,0,0,1);
				}
				else{
					return fixed4(0,1,0,1);
				}
			}
			ENDCG
		}
	}
}

Simply add a material using this shader to the unity ‘quad’ and it will be rendered as a perfect circle. This code is uncomplete, but it shows the concept :slight_smile:

EDIT: No anti aliasing sorry, but it can be added.

What your going to want to do for ALL OF THOSE

is use a plane

now to get the shape what your going to want to do is create a circle in photoshop that fits in a square and make the square itself White and the circle However you want just dont use white (this color can be changed used photoshop to any at all if you need to use white its just standard)

now your going to go create whats called an alpha channel.

That is basically where your going to pick 2 colors like say black and white and if its black in that area it’ll be fully visible and if its white it’ll be fully transparent and grey will be opaque.

You can then basically have it so while you have a plane you can only see the circle portion.

Look up on youtube and google alpha masks and alpha mask transparency and key words like that maybe combined with Gimp and photoshop (2 major tools for creating alpha masked textures)

this will give you tuts on how to do this.

Mark as answered and have a nice day!

Building off @Liquidwad’s answer, a simple shader that renders the circle on top of your sprite:

Shader "Custom/CircleOnTop" {
	Properties {
		[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}

		_Thickness("Thickness", Range(0.0,0.5)) = 0.05
		_Radius("Radius", Range(0.0, 0.5)) = 0.4
		_Dropoff("Dropoff", Range(0.01, 4)) = 0.1

		[HideInInspector] _StencilComp ("Stencil Comparison", Float) = 8
		[HideInInspector] _Stencil ("Stencil ID", Float) = 0
		[HideInInspector] _StencilOp ("Stencil Operation", Float) = 0
		[HideInInspector] _StencilWriteMask ("Stencil Write Mask", Float) = 255
		[HideInInspector] _StencilReadMask ("Stencil Read Mask", Float) = 255
		[HideInInspector] _ColorMask ("Color Mask", Float) = 15
		[HideInInspector] [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
	}

	SubShader {
		Tags
		{ 
			"Queue"="Transparent" 
			"IgnoreProjector"="True" 
			"RenderType"="Transparent" 
			"PreviewType"="Plane"
			"CanUseSpriteAtlas"="True"
		}
		
		Stencil
		{
			Ref [_Stencil]
			Comp [_StencilComp]
			Pass [_StencilOp] 
			ReadMask [_StencilReadMask]
			WriteMask [_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest [unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask [_ColorMask]

        Pass
		{
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP
			
			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				float2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			sampler2D _MainTex;
			float _Thickness;
			float _Radius;
			float _Dropoff;

			fixed4 _TextureSampleAdd;
			float4 _ClipRect;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
				OUT.texcoord = IN.texcoord;
				OUT.color = IN.color;

				return OUT;
			}

			float antialias(float radius, float dist, float thick, float drop) {
				if( dist < (radius - 0.5*thick))
					return 1 - pow( dist - radius + 0.5*thick,2)/ pow(drop*thick, 2);
				else if ( dist > (radius + 0.5*thick))
					return 1 - pow( dist - radius - 0.5*thick,2)/ pow(drop*thick, 2); 
				else
					return 1;
			}

			fixed4 frag(v2f IN) : SV_Target
			{
				half4 color;

				float distance = sqrt(pow(IN.texcoord.x - 0.5, 2) + pow(IN.texcoord.y - 0.5,2));
				float alias = antialias(_Radius, distance, _Thickness, _Dropoff);

				if(alias != 1){
					color = tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd;
					color.a *= IN.color.a;

					#ifdef UNITY_UI_ALPHACLIP
					clip (color.a - 0.001);
					#endif
				}
				else{
					color = IN.color;
					color.a = IN.color.a * alias;
				}

				return color;
			}

		ENDCG
		}
	}
}

The result is this:
Imgur

The alpha applies to the sprite as well, so if you set the Image.Color to Color.clear, both the sprite and the circle will disappear.