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!
Answer by Liquidwad · Nov 22, 2015 at 06:06 PM
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
}
}
}
add this before pass{} in subshader{} to add transparency
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
Blend SrcAlpha One$$anonymous$$inusSrcAlpha
and delete Blend SrcAlpha One$$anonymous$$inusSrcAlpha
from the pass{}
If you add the tags as suggested by Lucas$$anonymous$$lein16, you should also add ZWrite Off:
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
Blend SrcAlpha One$$anonymous$$inusSrcAlpha
ZWrite Off
Otherwise the object being rendered occludes other objects behind it.
Answer by W.Walter · Jun 23, 2015 at 04:37 AM
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 :)
EDIT: No anti aliasing sorry, but it can be added.
This seems overly complex just to draw a 2d primitive circle. Why is there not just a method that takes an x,y and diameter??
Welcome to computer graphics... You want a 'perfect' circle, which involves calculating each pixel separately. (which is what this shader does. For each screen pixel it calculates the distance to the origin of the uv coordinates, and if it is outside the diameter then the pixel is discarded). The only alternatives is creating a mesh programatically with as many vertices as you want for the desired resolution, or using a texture/alpha mask. This shader is probably the most efficiant of the three, and doesn't even take up graphics mem with a texture.
Brilliant, thanks. This is very useful for my space exploration project where the edges of planets need to be smooth at a very small scale compared to their full size.
Answer by Eric5h5 · Aug 23, 2013 at 02:12 AM
You can create meshes using the Mesh class. For a circle, use many polygons so the straight lines aren't distinguishable.
Answer by sparkzbarca · Aug 23, 2013 at 12:04 AM
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!
If I scale the plane up though won't the circle get pixellated?
only in relation to the texture size.
If you use a sufficiently sized resolution texture you would find it quite hard before it would become pixelated
Answer by arthursb · Mar 16, 2017 at 08:29 PM
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: http://imgur.com/zPG3pDT
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.