209 lines
7.0 KiB
HLSL
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 );
|
|
}
|
|
|
|
|