Skip to content

Instantly share code, notes, and snippets.

@mrkybe
Last active July 18, 2025 06:06
Show Gist options
  • Select an option

  • Save mrkybe/55500910578619e038d1ed9a8eac2249 to your computer and use it in GitHub Desktop.

Select an option

Save mrkybe/55500910578619e038d1ed9a8eac2249 to your computer and use it in GitHub Desktop.
Shader "Custom/SpriteColor"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
[HideInInspector] _Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
_PixelsPerUnit ("Pixels Per Unit", Float) = 16
_Scale ("Scale", Float) = 1
_Radius ("Radius", Float) = 8
_OutlineColor("Outline Color", Color) = (0,0,0,1)
_EdgeInnerColor("Edge Inner Color", Color) = (0.8,0.2,0,1)
_BellyColor("Belly Color", Color) = (1,1,1,1)
_FillColor("Fill Color", Color) = (0.5,0.5,0,1)
_Angle("Angle", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment SpriteFrag
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
fixed4 _Color;
float _PixelsPerUnit;
float _Scale;
float _Radius;
float4 _OutlineColor;
float4 _EdgeInnerColor;
float4 _BellyColor;
float4 _FillColor;
float _Angle;
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
v2f SpriteVert(appdata_t IN)
{
v2f OUT;
OUT.vertex = IN.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color;
return OUT;
}
sampler2D _MainTex;
sampler2D _AlphaTex;
fixed4 SampleSpriteTexture (float2 uv)
{
fixed4 color = tex2D (_MainTex, uv);
return color;
}
float4 bhams(float2 uv, float radius, float2 uCenter)
{
int2 pixelCoord = int2(uv.xy * _PixelsPerUnit);
float2 pcd = float2(0,0);
uCenter *= _PixelsPerUnit;
// Convert radius to integer
int r = int(floor(radius + 0.5));
bool isOnCircle = false;
bool isOutside = false;
float depth = 0;
float total = 0;
int2 c = int2(floor(uCenter + 0.5));
int2 quadrants[4] = {
int2(+1, +1),
int2(-1, +1),
int2(+1, -1),
int2(-1, -1)
};
int i = 0;
// Initialize Bresenham's variables
int x = 0;
int y = r;
int d = 3 - 2 * r;
// reset Bresenham variables
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = x * quadrants[0].x;
int my = y * quadrants[0].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x <= c.x + mx && pixelCoord.y <= c.y + my &&
pixelCoord.x >= c.x && pixelCoord.y >= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = x * quadrants[1].x;
int my = y * quadrants[1].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x >= c.x + mx && pixelCoord.y <= c.y + my &&
pixelCoord.x <= c.x && pixelCoord.y >= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = x * quadrants[2].x;
int my = y * quadrants[2].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x <= c.x + mx && pixelCoord.y >= c.y + my &&
pixelCoord.x >= c.x && pixelCoord.y <= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = x * quadrants[3].x;
int my = y * quadrants[3].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x >= c.x + mx && pixelCoord.y >= c.y + my &&
pixelCoord.x <= c.x && pixelCoord.y <= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
/////////////////////////////
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = y * quadrants[0].x;
int my = x * quadrants[0].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x <= c.x + mx && pixelCoord.y <= c.y + my &&
pixelCoord.x >= c.x && pixelCoord.y >= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = y * quadrants[1].x;
int my = x * quadrants[1].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x >= c.x + mx && pixelCoord.y <= c.y + my &&
pixelCoord.x <= c.x && pixelCoord.y >= c.y)
{
pcd.x += 0.5;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = y * quadrants[2].x;
int my = x * quadrants[2].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x <= c.x + mx && pixelCoord.y >= c.y + my &&
pixelCoord.x >= c.x && pixelCoord.y <= c.y)
{
pcd.x = 1;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
x = 0;
y = r;
d = 3 - 2 * r;
while (x <= y)
{
int mx = y * quadrants[3].x;
int my = x * quadrants[3].y;
if(pixelCoord.x == c.x + mx && pixelCoord.y == c.y + my)
{
isOnCircle = true;
}
if(pixelCoord.x >= c.x + mx && pixelCoord.y >= c.y + my &&
pixelCoord.x <= c.x && pixelCoord.y <= c.y)
{
pcd.x = 1;
}
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
// Run the Bresenham circle algorithm steps
/*
while (x <= y)
{
if (all(pixelCoord == int2(c.x + x, c.y + y)) ||
all(pixelCoord == int2(c.x - x, c.y + y)) ||
all(pixelCoord == int2(c.x + x, c.y - y)) ||
all(pixelCoord == int2(c.x - x, c.y - y)) ||
all(pixelCoord == int2(c.x + y, c.y + x)) ||
all(pixelCoord == int2(c.x - y, c.y + x)) ||
all(pixelCoord == int2(c.x + y, c.y - x)) ||
all(pixelCoord == int2(c.x - y, c.y - x)))
{
isOnCircle = true;
break;
}
// Advance the Bresenham decision variable
if (d < 0)
{
d += 4 * x + 6;
}
else
{
d += 4 * (x - y) + 10;
y--;
}
x++;
}
*/
// Output color if the pixel is on the circle, else discard
return float4(isOnCircle, pcd.x, 0, isOnCircle || pcd.x > 0);
}
fixed4 SpriteFrag(v2f IN) : SV_Target
{
float2 center = float2(0.5, 0.5);
float4 ol1 = bhams(IN.texcoord, _Radius, center);
float4 ol2 = bhams(IN.texcoord, _Radius, center);
float olmix = (ol1.r || ol2.r) || (ol1.g && !ol2.g);
float4 outline = _OutlineColor * olmix;
float4 il1 = bhams(IN.texcoord, _Radius - (_Radius < 16.5 ? 0 : 0), center);
float4 il2 = bhams(IN.texcoord, _Radius - (_Radius < 16.5 ? 1 : 1), center);
float ilmix = (il1.r || il2.r) || (il1.g && !il2.g);
float4 in_line = _EdgeInnerColor * ilmix;
float2 bellyPos = float2(sin(_Angle), cos(_Angle)) * (floor(_Radius)/100);
float4 belly = bhams(IN.texcoord, _Radius, center + bellyPos);
float bellymix = ((belly.r || belly.g) && ol1.g) && !olmix;
float bellyLinemix = ((belly.r || belly.g) && ilmix) && !olmix;
belly = _BellyColor * (bellymix - bellyLinemix) + _FillColor * bellyLinemix;
float4 fcol = outline + in_line * abs(outline.a - in_line.a);
float4 ffcol = belly + fcol * abs(fcol.a - belly.a);
float fmix = !ffcol.a && ol2.g;
float4 fill = _FillColor * fmix;
return saturate(ffcol + fill);
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment