Files
aibis-dream/Assets/Shapes/Shaders/Core/Disc Core.cginc
T
2025-05-07 15:07:44 +08:00

342 lines
12 KiB
HLSL

// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
#include "UnityCG.cginc"
#include "../Shapes.cginc"
#pragma target 3.0
UNITY_INSTANCING_BUFFER_START(Props)
PROP_DEF(int, _ScaleMode)
PROP_DEF(int, _Alignment)
PROP_DEF(half4, _Color)
PROP_DEF(half4, _ColorOuterStart)
PROP_DEF(half4, _ColorInnerEnd)
PROP_DEF(half4, _ColorOuterEnd)
PROP_DEF(half, _Radius)
PROP_DEF(int, _RadiusSpace)
PROP_DEF(half, _Thickness)
PROP_DEF(int, _ThicknessSpace)
PROP_DEF(half, _AngleStart)
PROP_DEF(half, _AngleEnd)
PROP_DEF(int, _RoundCaps)
#ifdef INNER_RADIUS
SHAPES_DASH_PROPERTIES
#endif
UNITY_INSTANCING_BUFFER_END(Props)
#ifdef INNER_RADIUS
#include "../DashUtils.cginc"
#endif
#define ALIGNMENT_FLAT 0
#define ALIGNMENT_BILLBOARD 1
#define IP_uv0 intp0.xy
#define IP_pxCoverage intp0.z
#define IP_innerRadiusFraction intp0.w
#define IP_centerRadiusMeters intp1.x
#define IP_thicknessMeters intp1.y
#define IP_pxPerMeter intp1.z
#define IP_uniformScale intp1.w
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv0 : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 pos : SV_POSITION;
half4 intp0 : TEXCOORD0;
#ifdef INNER_RADIUS
half4 intp1 : TEXCOORD1;
#endif
UNITY_FOG_COORDS(2)
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
// I hate C
inline void ApplyRadialMask( inout half mask, VertexOutput i, out half tRadial );
inline void ApplyAngularMask( inout half mask, VertexOutput i, out float tAngularFull, out float tAngular, out half2 coord, out float ang, out half angStart, out half angEnd, out bool useRoundCaps, out half sectorSize );
inline void ApplyEndCaps( inout half mask, VertexOutput i, half2 coord, half ang, half angStart, half angEnd, bool useRoundCaps );
inline void ApplyDashes( inout half mask, VertexOutput i, float t, half tRadial, half sectorSize );
inline half4 GetColor( half tRadial, half tAngular );
inline half4 GetBounds( bool centerAlign, half alignOffset, half angDelta, half innerFrac, half outerFrac );
VertexOutput vert (VertexInput v) {
UNITY_SETUP_INSTANCE_ID(v);
VertexOutput o = (VertexOutput)0;
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
half uniformScale = GetUniformScale();
half radius = PROP(_Radius) * uniformScale;
int radiusSpace = PROP(_RadiusSpace);
LineWidthData widthDataRadius = GetScreenSpaceWidthDataSimple( OBJ_ORIGIN, CAM_RIGHT, radius*2, radiusSpace );
o.IP_pxCoverage = widthDataRadius.thicknessPixelsTarget;
// padding correction
half paddingMeters = AA_PADDING_PX/widthDataRadius.pxPerMeter;
half radiusInMeters = widthDataRadius.thicknessMeters / 2; // actually, center radius
half vertexRadius;
half outerRadiusFraction;
half innerRadiusFraction = 0;
#ifdef INNER_RADIUS
o.IP_pxPerMeter = widthDataRadius.pxPerMeter;
int scaleMode = PROP(_ScaleMode);
half scaleThickness = scaleMode == SCALE_MODE_UNIFORM ? uniformScale : 1;
half thickness = PROP(_Thickness) * scaleThickness;
int thicknessSpace = PROP(_ThicknessSpace);
LineWidthData widthDataThickness = GetScreenSpaceWidthDataSimple( OBJ_ORIGIN, CAM_RIGHT, thickness, thicknessSpace );
half thicknessRadius = widthDataThickness.thicknessMeters / 2;
o.IP_thicknessMeters = widthDataThickness.thicknessMeters;
o.IP_pxCoverage = widthDataThickness.thicknessPixelsTarget; // todo: this isn't properly handling coordinate scaling yet
half radiusOuter = radiusInMeters + thicknessRadius;
vertexRadius = radiusOuter + paddingMeters;
outerRadiusFraction = radiusOuter / vertexRadius;
o.IP_innerRadiusFraction = (radiusOuter - thicknessRadius*2) / radiusOuter;
innerRadiusFraction = o.IP_innerRadiusFraction;
o.IP_centerRadiusMeters = radiusInMeters;
o.IP_uniformScale = uniformScale;
#else
vertexRadius = radiusInMeters + paddingMeters;
outerRadiusFraction = radiusInMeters / vertexRadius;
#endif
// change the bounding box based on angles to save fill rate performance
#ifdef SECTOR
half angStartRaw = PROP(_AngleStart);
half angEndRaw = PROP(_AngleEnd);
half angStart = min(angStartRaw,angEndRaw);
half angEnd = max(angStartRaw,angEndRaw);
half angDeltaRaw = angEnd - angStart;
half angDelta = clamp( angDeltaRaw, -TAU, TAU );
half angSpan = abs(angDelta);
half alignOffset = (angSpan-TAU/2)*0.5;
bool centerAlign = true;
#ifndef INNER_RADIUS
if( angSpan <= TAU/2 ) { // don't center align pies until more than half a turn
alignOffset = 0; // to corner-align pies better
centerAlign = false;
}
#endif
half4 bounds = GetBounds( centerAlign, alignOffset, angDelta, innerRadiusFraction, outerRadiusFraction );
v.uv0 = Remap( half2(-1,-1), half2(1,1), bounds.xy, bounds.zw, v.uv0 );
v.uv0 = Rotate(v.uv0, angStart + alignOffset);
#endif
v.vertex.xy = v.uv0 * vertexRadius;
v.uv0 /= outerRadiusFraction; // padding correction
if( PROP(_Alignment) == ALIGNMENT_BILLBOARD ) {
half3 frw = WorldToLocalVec( -DirectionToNearPlanePos( OBJ_ORIGIN ) );
half3 camRightLocal = WorldToLocalVec( CAM_RIGHT );
half3 up = normalize( cross( frw, camRightLocal ) );
half3 right = cross( up, frw ); // already normalized
v.vertex.xyz = v.vertex.x * right + v.vertex.y * up;
}
o.IP_uv0 = v.uv0;
o.pos = UnityObjectToClipPos( v.vertex / uniformScale );
UNITY_TRANSFER_FOG(o,o.pos);
return o;
}
FRAG_OUTPUT_V4 frag( VertexOutput i ) : SV_Target {
UNITY_SETUP_INSTANCE_ID(i);
half tRadial;
float tAngular; // interpolators for radial & angular gradient
float tAngularFull; // angular gradient 0 to 1, used for dashes
float ang;
half angStart, angEnd;
bool useRoundCaps;
half2 coord; // coordinates used for end caps, if applicable
half sectorSize; // used to snap dash coords
half mask = 1;
ApplyRadialMask( /*inout*/ mask, i, /*out*/ tRadial );
ApplyAngularMask( /*inout*/ mask, i,/*out*/ tAngularFull, /*out*/ tAngular, /*out*/ coord, /*out*/ ang, /*out*/ angStart, /*out*/ angEnd, /*out*/ useRoundCaps, /*out*/ sectorSize );
#ifdef INNER_RADIUS
ApplyEndCaps(/*inout*/ mask, i, coord, ang, angStart, angEnd, useRoundCaps);
ApplyDashes( /*inout*/ mask, i, tAngularFull, tRadial, sectorSize );
#endif
mask *= saturate(i.IP_pxCoverage); // pixel fade
half4 color = GetColor( tRadial, tAngular );
return SHAPES_OUTPUT( color, mask, i );
}
// minX, minY, maxX, maxY
inline half4 GetBounds( bool centerAlign, half alignOffset, half angDelta, half innerFrac, half outerFrac ) {
half laaPadding = -(1-outerFrac);
half yMinRootRef = laaPadding; // padded for LAA
half aSpan = abs(angDelta);
half2 dir = AngToDir(aSpan - alignOffset);
half2 tipMin = min(0,dir);
half tipMaxY = dir.y;
#ifdef INNER_RADIUS
bool roundCaps = PROP( _RoundCaps );
if(roundCaps){
half uvRingRadius = (1-innerFrac)*0.5-laaPadding;
half2 dirSclCnt = dir*(1-uvRingRadius);
yMinRootRef -= uvRingRadius;
tipMin = dirSclCnt-uvRingRadius.xx;
tipMaxY = dirSclCnt.y+uvRingRadius;
} else {
half2 dirSclInner = dir*innerFrac+laaPadding;
tipMin = min(dirSclInner, dir);
}
#endif
half2 minC, maxC;
if( centerAlign ){ // arc center always at the top
minC.x = aSpan > TAU*0.50 ? -1 : tipMin.x;
minC.y = tipMin.y;
maxC.x = -minC.x;
maxC.y = 1;
} else {
minC.x = aSpan > TAU*0.50 ? -1 : tipMin.x;
half y0 = aSpan > TAU*0.75 ? tipMin.y : min(tipMin.y, yMinRootRef);
half y1 = aSpan > TAU*0.25 ? 1 : tipMaxY;
minC.y = min(y0,y1);
maxC.y = max(y0,y1);
maxC.x = 1; // start angle always fully to the right
}
return half4(minC, maxC);
}
inline half ArcLengthToAngle( half radius, half arcLength ){
return arcLength / radius;
}
#ifdef INNER_RADIUS
inline void ApplyDashes( inout half mask, VertexOutput i, float t, half tRadial, half angularSpan ){
if( IsDashed() ) {
half radiusMeters = i.IP_centerRadiusMeters;
float dist = t * radiusMeters * TAU; // arc length in meters right now
float distTotal = radiusMeters * angularSpan;
DashConfig dash = GetDashConfig( i.IP_uniformScale );
DashCoordinates dashCoords = GetDashCoordinates( dash, dist, distTotal, i.IP_thicknessMeters, i.IP_pxPerMeter );
ApplyDashMask( /*inout*/ mask, dashCoords, tRadial*2-1, dash.type, dash.modifier );
}
}
#endif
inline void ApplyRadialMask( inout half mask, VertexOutput i, out half tRadial ){
half len = length( i.IP_uv0 );
mask = min( mask, StepAA( len, 1 ) ); // outer radius
#ifdef INNER_RADIUS
mask = min( mask, 1.0-StepAA( len, i.IP_innerRadiusFraction ) ); // inner radius
tRadial = saturate(InverseLerp( i.IP_innerRadiusFraction, 1, len ) );
#else
tRadial = saturate(len);
#endif
}
inline void ApplyAngularMask( inout half mask, VertexOutput i, out float tAngularFull, out float tAngular, out half2 coord, out float ang, out half angStart, out half angEnd, out bool useRoundCaps, out half sectorSize ){
#ifdef SECTOR
angStart = PROP(_AngleStart);
angEnd = PROP(_AngleEnd);
// Rotate so that the -pi/pi seam is opposite of the visible segment
// 0 is the center of the segment post-rotate
half angOffset = -(angEnd + angStart) * 0.5;
coord = Rotate( i.IP_uv0, angOffset );
angStart += angOffset;
angEnd += angOffset;
#else
// required for angular gradients on rings and discs
angStart = 0;
angEnd = TAU;
coord = -i.IP_uv0;
#endif
half angDelta = clamp( angEnd - angStart, -TAU, TAU );
coord.y *= sign(angDelta); // since start/end is flipped for reversed situations
ang = atan2( coord.y, coord.x ); // -pi to pi
sectorSize = abs(angDelta);
tAngular = saturate(ang/sectorSize + 0.5); // angular interpolator for color
#ifdef SECTOR
useRoundCaps = PROP( _RoundCaps );
float segmentMask;
#if LOCAL_ANTI_ALIASING_QUALITY == 0
segmentMask = StepAA( abs(ang), sectorSize*0.5 );
#else
// if arc
#ifdef INNER_RADIUS
if( useRoundCaps ){ // arcs with round caps hide the border anyway, so use cheap version
segmentMask = step( abs(ang), sectorSize*0.5 );
} else {
#endif
float2 pdCoordSpace = float2( -coord.y, coord.x ) / dot( coord, coord );
segmentMask = StepAAManualPD( coord, abs(ang), sectorSize*0.5, pdCoordSpace );
// if arc
#ifdef INNER_RADIUS
} // this is a little cursed I know I'm sorry~
#endif
#endif
// Adjust if close to 0 or TAU radians, fade in or out completely
half THRESH_INVIS = 0.001;
half THRESH_VIS = 0.002;
half fadeInMask = saturate( InverseLerp( TAU - THRESH_VIS, TAU - THRESH_INVIS, sectorSize ) );
half fadeOutMask = saturate( InverseLerp( THRESH_INVIS, THRESH_VIS, sectorSize ) );
mask *= lerp( segmentMask * fadeOutMask, 1, fadeInMask );
#else
// SECTOR not defined
useRoundCaps = false;
#endif
// used for dashes
#ifdef INNER_RADIUS
tAngularFull = (ang + sectorSize/2)/TAU;
#else
tAngularFull = 0;
#endif
}
inline void ApplyEndCaps( inout half mask, VertexOutput i, half2 coord, half ang, half angStart, half angEnd, bool useRoundCaps ){
#if defined(INNER_RADIUS) && defined(SECTOR)
if( useRoundCaps ){
half halfThickness = (1-i.IP_innerRadiusFraction)/2;
half distToCenterOfRing = 1-halfThickness;
half angToA = abs( ang - angStart );
half angToB = abs( ang - angEnd );
half capAng = (angToA < angToB) ? angStart : angEnd;
half2 capCenter = AngToDir(capAng) * distToCenterOfRing;
half endCapMask = StepAA( length(coord - capCenter), halfThickness );
mask = max(mask, endCapMask);
}
#endif
}
inline half4 GetColor( half tRadial, half tAngular ){
half4 colInnerStart = PROP(_Color);
half4 colOuterStart = PROP(_ColorOuterStart);
half4 colInnerEnd = PROP(_ColorInnerEnd);
half4 colOuterEnd = PROP(_ColorOuterEnd);
half4 colorStart = lerp( colInnerStart, colOuterStart, tRadial );
half4 colorEnd = lerp( colInnerEnd, colOuterEnd, tRadial );
return lerp( colorStart, colorEnd, tAngular );
}