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

209 lines
7.0 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(half, _Radius)
PROP_DEF(int, _RadiusSpace)
PROP_DEF(half, _Thickness)
PROP_DEF(int, _ThicknessSpace)
PROP_DEF(half, _Angle)
PROP_DEF(half, _Roundness)
PROP_DEF(int, _Hollow)
PROP_DEF(int, _Sides)
SHAPES_DASH_PROPERTIES
SHAPES_FILL_PROPERTIES
UNITY_INSTANCING_BUFFER_END(Props)
#include "../DashUtils.cginc"
#include "../FillUtils.cginc"
#define ALIGNMENT_FLAT 0
#define ALIGNMENT_BILLBOARD 1
#define IP_uv0 intp0.xy
#define IP_pxPerMeter intp0.z
#define IP_apothem intp0.w
#define IP_innerRadiusFraction intp1.x
#define IP_pxCoverage intp1.y
#define IP_angHalfSegment intp1.z
#define IP_halfSideLength intp1.w
#define IP_thicknessMeters intp2.x
#define IP_radiusMeters intp2.y
#define IP_uniformScale intp2.z
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;
half4 intp1 : TEXCOORD1;
half3 intp2 : TEXCOORD2;
SHAPES_INTERPOLATOR_FILL(3)
UNITY_FOG_COORDS(4)
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
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);
half2 objScale = GetObjectScaleXY();
o.IP_uniformScale = GetUniformScale(objScale);
float radius = PROP(_Radius) * o.IP_uniformScale;
float radiusSpace = PROP(_RadiusSpace);
LineWidthData widthDataRadius = GetScreenSpaceWidthDataSimple( OBJ_ORIGIN, CAM_RIGHT, radius*2, radiusSpace );
o.IP_pxCoverage = widthDataRadius.thicknessPixelsTarget;
o.IP_radiusMeters = widthDataRadius.thicknessMeters / 2; // actually, center radius
bool hollow = PROP(_Hollow) == 1;
if( hollow ) {
o.IP_pxPerMeter = widthDataRadius.pxPerMeter;
int scaleMode = PROP(_ScaleMode);
half scaleThickness = scaleMode == SCALE_MODE_UNIFORM ? o.IP_uniformScale : 1;
float thickness = PROP(_Thickness) * scaleThickness;
float thicknessSpace = PROP(_ThicknessSpace);
LineWidthData widthDataThickness = GetScreenSpaceWidthDataSimple( OBJ_ORIGIN, CAM_RIGHT, thickness, thicknessSpace );
half thicknessRadius = widthDataThickness.thicknessMeters / 2;
o.IP_pxCoverage = widthDataThickness.thicknessPixelsTarget; // todo: this isn't properly handling coordinate scaling yet
half radiusOuter = o.IP_radiusMeters + thicknessRadius;
o.IP_innerRadiusFraction = (radiusOuter - thicknessRadius*2) / radiusOuter;
o.IP_thicknessMeters = widthDataThickness.thicknessMeters;
v.vertex.xy = v.uv0 * radiusOuter;
} else {
v.vertex.xy = v.uv0 * o.IP_radiusMeters;
}
v.vertex.xy = Rotate(v.vertex.xy, PROP(_Angle));
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;
}
float n = PROP(_Sides);
half angSegment = TAU/n;
o.IP_angHalfSegment = angSegment/2;
o.IP_apothem = cos( o.IP_angHalfSegment );
o.IP_halfSideLength = sin( o.IP_angHalfSegment );
o.IP_uv0 = v.uv0;
v.vertex.xy /= objScale;
o.pos = UnityObjectToClipPos( v.vertex );
UNITY_TRANSFER_FOG(o,o.pos);
o.fillCoords = GetFillCoords( v.vertex );
return o;
}
inline half GetRadialMask( VertexOutput i, out float tLateral ){
float n = PROP(_Sides);
half angSegment = TAU/n;
half2 coords = i.IP_uv0;
half roundness = PROP(_Roundness);
half roundnessInv = 1.0-roundness;
half sdf = SdfNgon( angSegment, i.IP_apothem*roundnessInv, i.IP_halfSideLength*roundnessInv, coords );
half mask = StepAA( sdf, roundness*i.IP_apothem ); // outer radius
bool hollow = PROP(_Hollow) == 1;
if( hollow ) {
float sdfInner = sdf + 1-i.IP_innerRadiusFraction;
tLateral = (sdf - roundness*i.IP_apothem)/(1-i.IP_innerRadiusFraction)+1;
mask = min( mask, 1.0-StepAA( sdfInner, roundness*i.IP_apothem ) ); // inner radius
}
return mask;
}
void GetPerimeterDistance( VertexOutput i, out float dist, out float distTotal, float2 coord ) {
int n = PROP(_Sides);
bool snapEndToEnd = PROP(_DashSnap) == DASH_SNAP_ENDTOEND;
int sector = floor( n * ( DirToAng( coord ) / TAU ) );
if( sector < 0 )
sector += n;
float2 p = Rotate( coord, -sector * TAU / n );
float angHalfSegment = TAU / (2*n);
float apothem = cos( angHalfSegment );
float halfSideLength = sin( angHalfSegment );
float2 normal = float2( apothem, halfSideLength );
float sideLengthFlat = halfSideLength * 2;
float2 scNormal = normal * sideLengthFlat;
float pScNormDeterminant = Determinant( scNormal, p );
bool isInSecondHalf = pScNormDeterminant > 0;
if( isInSecondHalf )
p = reflect( -p, normal );
float projT = saturate( 0.5f - abs( pScNormDeterminant ) / ( sideLengthFlat * sideLengthFlat ) );
float projectedDist = projT * sideLengthFlat;
// radius = 1 during these calcs
float roundness = PROP(_Roundness);
float roundnessRadius = apothem * roundness;
float roundnessStartOffset = halfSideLength * roundness;
float localHalfArcLen = ( TAU / ( 2 * n ) ) * roundnessRadius;
float sectorPerimeter = localHalfArcLen * 2 + ( sideLengthFlat - roundnessStartOffset * 2 );
// full distance around the whole thing
distTotal = snapEndToEnd ? sectorPerimeter : sectorPerimeter * n;
dist = snapEndToEnd ? 0 : sectorPerimeter * sector; // add previous sectors
float localPerimeterDist = 0;
if( projectedDist < roundnessStartOffset ) {
// inside the first arc bit
float ang = DirToAng( p - float2( 1.0 - roundness, 0 ) );
float arc = ang * roundnessRadius;
localPerimeterDist += arc;
} else {
// after the arc, on the edge
localPerimeterDist += localHalfArcLen;
localPerimeterDist += projectedDist - roundnessStartOffset;
}
dist += isInSecondHalf ? sectorPerimeter - localPerimeterDist : localPerimeterDist; // add local perimeter progress
// scale by radius
half r = i.IP_radiusMeters;
dist *= r;
distTotal *= r;
}
FRAG_OUTPUT_V4 frag( VertexOutput i ) : SV_Target {
UNITY_SETUP_INSTANCE_ID(i);
float tRadial = 0;
half mask = GetRadialMask( i, /*out*/ tRadial );
//return float4(tRadial.xxx,1);
if( IsBetween( tRadial, -1, 2 ) && IsDashed() && PROP(_Hollow) > 0 ) {
float dist, distTotal;
GetPerimeterDistance( i, /*out*/ dist, /*out*/ distTotal, i.IP_uv0 );
DashConfig dash = GetDashConfig( i.IP_uniformScale, /*periodicEndToEnd = */true );
DashCoordinates dashData = GetDashCoordinates( dash, dist, distTotal, (1-i.IP_innerRadiusFraction)*i.IP_uniformScale, i.IP_pxPerMeter );
ApplyDashMask( /*inout*/ mask, dashData, tRadial*2-1, dash.type, dash.modifier );
}
mask *= saturate(i.IP_pxCoverage); // pixel fade
half4 fillColor = GetFillColor(i.fillCoords);
return SHAPES_OUTPUT( fillColor, mask, i );
}