Skip to content

Instantly share code, notes, and snippets.

@bgolus
Created May 27, 2020 08:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bgolus/ceb5ec7827257b6094d72f6263e98cd2 to your computer and use it in GitHub Desktop.
Save bgolus/ceb5ec7827257b6094d72f6263e98cd2 to your computer and use it in GitHub Desktop.
Circular Bar Shaders: Rotated Mask with Additional Features
Shader "CircularBar" {
Properties {
_Frac ("Progress Bar Value", Range(0,1)) = 1.0
_FillColor ("Fill Color", Color) = (1,1,1,1)
_BackColor ("Background Color", Color) = (0,0,0,1)
[Header(Ring)]
_OuterRadius ("Outer Radius", Range(0,1)) = 0.95
_InnerRadius ("Inner Radius", Range(0,1)) = 0.5
[Header(Outline)]
[Toggle(USE_OUTLINE)] _UseOutline ("Enable", Float) = 0.0
_OutlineColor ("Color", Color) = (0,0,0,1)
_OutlineWidth ("Width", Range(0,1)) = 0.1
[Header(Arc)]
[Toggle(USE_ARC)] _UseArc ("Enable", Float) = 0.0
_ArcAngle ("Angle", Range(-180,180)) = 0.0
_ArcRange ("Range", Range(0,1)) = 0.75
[Space(10)]
[Toggle] _FlipU ("Flip Horizontal", Float) = 0.0
[Toggle] _FlipV ("Flip Vertical", Float) = 0.0
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "DisableBatching"="True"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature USE_OUTLINE
#pragma shader_feature USE_ARC
#include "UnityCG.cginc"
// Direct3D compiled stats:
// vertex shader:
// 11 math w/o arc
// 20 math w/ arc
// fragment shader:
// 28 math w/o arc or outline
// 35 math w/ arc
// 37 math w/ outline
// 48 math w/ arc and outline
half _Frac;
fixed4 _FillColor;
fixed4 _BackColor;
fixed4 _OutlineColor;
half _OuterRadius;
half _InnerRadius;
half _OutlineWidth;
bool _FlipU;
bool _FlipV;
half _ArcAngle;
half _ArcRange;
struct v2f {
float4 pos : SV_POSITION;
#if defined(USE_ARC)
float4 uvMask : TEXCOORD0;
#else
float3 uvMask : TEXCOORD0;
#endif
};
v2f vert (appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvMask.xy = v.texcoord.xy * 2.0 - 1.0;
// options for flipping the direction
if (_FlipU)
o.uvMask.x = -o.uvMask.x;
if (_FlipV)
o.uvMask.y = -o.uvMask.y;
float barFrac = _Frac;
float sinX, cosX;
#if defined(USE_ARC)
// note: there are no protections for very narrow arcs!
// rotate base masks by arc angle
float minAngle = _ArcAngle * (UNITY_PI / 180.0) + (1.0 - _ArcRange) * UNITY_PI;
sincos(minAngle, sinX, cosX);
float2x2 minRotationMatrix = float2x2(cosX, -sinX, sinX, cosX);
o.uvMask.xy = mul(o.uvMask.xy, minRotationMatrix);
// rotated mask for end of arc
float maxAngle = _ArcRange * (UNITY_PI * 2.0);
sincos(maxAngle, sinX, cosX);
float2x2 maxRotationMatrix = float2x2(cosX, -sinX, sinX, cosX);
o.uvMask.w = -mul(o.uvMask.xy, maxRotationMatrix).x;
// scale progress bar value based on arc range
barFrac *= _ArcRange;
#endif // USE_ARC
// angled mask for end of progress bar
float angle = barFrac * (UNITY_PI * 2.0) - UNITY_PI;
sincos(angle, sinX, cosX);
float2x2 rotationMatrix = float2x2(cosX, -sinX, sinX, cosX);
o.uvMask.z = mul(o.uvMask.xy, rotationMatrix).x;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half barFrac = _Frac;
// radial gradient for circles
half radialGrad = length(i.uvMask.xy);
// accurate derivative length rather than fwidth
half radialGradDeriv = length(half2(ddx(radialGrad), ddy(radialGrad))) * 0.75;
// outer and inner circle masks for progress bar
half outerEdge = _OuterRadius - radialGrad;
half innerEdge = radialGrad - _InnerRadius;
// progress bar circle edge mask
half circleEdge = smoothstep(-radialGradDeriv, radialGradDeriv, min(outerEdge, innerEdge));
#if defined(USE_OUTLINE)
// outline circle edge mask
half outlineEdge = max(smoothstep(-radialGradDeriv, radialGradDeriv, min(outerEdge, innerEdge) + _OutlineWidth), circleEdge);
#endif // USE_OUTLINE
// sharpen masks with screen space derivates
half diag = i.uvMask.z / fwidth(i.uvMask.z);
half vert = i.uvMask.x / fwidth(i.uvMask.x);
#if defined(USE_ARC)
// scale progress bar value based on arc range
barFrac *= _ArcRange;
// get arc end
half arc_max_edge = i.uvMask.w / fwidth(i.uvMask.w) + 0.5;
// init arc edges for outline
half arc_outline_min = 0;
half arc_outline_max = 0;
#if defined(USE_OUTLINE)
// set offset arc edges for outline
arc_outline_min = (i.uvMask.x - _OutlineWidth) / fwidth(i.uvMask.x);
arc_outline_max = (i.uvMask.w - _OutlineWidth) / fwidth(i.uvMask.w);
#endif // USE_OUTLINE
// arc masks for circle and outline edge mask
half circleArcMask = 0;
half outlineArcMask = 0;
if ((_ArcRange) < 1.0)
{
// "flip" arc mask depending on if less than 180 degrees
if ((_ArcRange) < 0.5)
{
// arc is wedge
circleArcMask = max(vert, arc_max_edge);
outlineArcMask = max(arc_outline_min, arc_outline_max);
}
else
{
// remove wedge
circleArcMask = min(vert, arc_max_edge);
outlineArcMask = min(arc_outline_min, arc_outline_max);
}
// cut out arc wedge from circle edge mask
circleEdge = min(circleEdge, 1.0 - saturate(circleArcMask));
#if defined(USE_OUTLINE)
// cut out arc wedge from outline edge mask
outlineEdge = min(outlineEdge, 1.0 - saturate(outlineArcMask));
#endif // USE_OUTLINE
// hack to prevent color bleed at the starting edge of the progress bar
vert -= 0.5;
}
#endif // USE_ARC
// "flip" the masks depending on progress bar value
half barProgress = 0.0;
if (barFrac < 0.5)
barProgress = max(diag, vert);
else
barProgress = min(diag, vert);
// mask bottom of progress bar when below 20%
if (barFrac < 0.2 && i.uvMask.y < 0.0)
barProgress = 1.0;
barProgress = saturate(barProgress);
// lerp between colors
fixed4 col = lerp(_FillColor, _BackColor, barProgress);
#if defined(USE_OUTLINE)
// lerp to outline color if outline > 0.0
if (_OutlineWidth > 0.0)
col = lerp(_OutlineColor, col, circleEdge);
// apply outline mask as alpha
col.a *= outlineEdge;
#else // !defined(USE_OUTLINE)
// apply circle mask as alpha
col.a *= circleEdge;
#endif // USE_OUTLINE
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment