Files
2025-05-07 15:07:44 +08:00

150 lines
5.2 KiB
HLSL

// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
#define DASH_TYPE_BASIC 0
#define DASH_TYPE_ANGLED 1
#define DASH_TYPE_ROUND 2
#define DASH_SPACE_FIXED_COUNT -2
#define DASH_SPACE_RELATIVE -1
#define DASH_SPACE_METERS 0
#define DASH_SNAP_OFF 0
#define DASH_SNAP_TILING 1
#define DASH_SNAP_ENDTOEND 2
#define DASH_SNAP_ENDTOEND_PERIODIC 3 // note: only used in shader code to branch behavior, not in C#
// all the dash properties
struct DashConfig {
half size;
half spacing;
half offset;
int space;
int thicknessSpace;
int snap;
int type;
half modifier;
};
// coordinates used when rendering dashes in the fragment shader
struct DashCoordinates{
half coord;
half spacePerPeriod;
half thicknessPerPeriod;
};
DashConfig GetDashConfig( half uniformScale, bool periodicEndToEnd = false ) {
DashConfig d;
d.size = PROP( _DashSize );
d.spacing = PROP( _DashSpacing );
d.space = PROP( _DashSpace );
d.offset = PROP( _DashOffset );
d.thicknessSpace = PROP( _ThicknessSpace );
d.snap = PROP( _DashSnap );
if( d.snap == DASH_SNAP_ENDTOEND && periodicEndToEnd )
d.snap = DASH_SNAP_ENDTOEND_PERIODIC;
d.type = PROP( _DashType );
d.modifier = PROP( _DashShapeModifier );
// scale size/spacing when using uniform scaling w. relative/meters
int scaleMode = PROP(_ScaleMode);
if( scaleMode == SCALE_MODE_UNIFORM && d.space != DASH_SPACE_FIXED_COUNT ) {
d.size *= uniformScale;
d.spacing *= uniformScale;
}
return d;
}
// this works in normalized space, repeating integers for every period
inline void ApplyDashMask( inout half shape_mask, DashCoordinates dashCoords, half coordAcross, int type, half dashModifier ){
half spacePerPeriod = dashCoords.spacePerPeriod;
if( spacePerPeriod >= 1.0-VERY_SMOL ){
shape_mask = 0; // pretty much just space, make invisible
// the flipside of this (only dashes, no space)
// is actually not gapless for rounded dashes by design,
// so we can't just return solid/no masking naively
} else {
half thicknessPerPeriod = dashCoords.thicknessPerPeriod;
half2 coord = half2( coordAcross, dashCoords.coord );
if( type == DASH_TYPE_ANGLED )
coord.y += 0.5*coord.x*thicknessPerPeriod*dashModifier; // 45° angle skewing
half dashSdf = abs(frac(coord.y) * 2 - 1); // triangle wave
dashSdf = InverseLerp( spacePerPeriod, 1, dashSdf ); // convert to SDF matching dash ratio
half dashMask;
if( type == DASH_TYPE_ROUND ){
half dashPerPeriod = 1.0-spacePerPeriod;
half dashPerThickness = dashPerPeriod/thicknessPerPeriod;
half lenCoord = 1.0 - dashSdf*dashPerThickness;
half2 roundnoot = half2(saturate(lenCoord), coord.x);
half sdfRound = length(half2(lenCoord, coord.x))-1;
half maskRounds = 1.0-saturate( sdfRound / fwidth(sdfRound) + 0.5 ); // 0.5 offset to center on pixel bounds
half maskFill = saturate( (dashSdf - 1/dashPerThickness) / fwidth(dashSdf) + 0.5 ); // 0.5 offset to center on pixel bounds
dashMask = max(maskFill, maskRounds);
} else {
dashMask = saturate( dashSdf / fwidth(dashSdf) + 0.5 ); // 0.5 offset to center on pixel bounds
}
shape_mask = min(shape_mask, dashMask);
}
}
// returns snapped period count
inline half DashSnap( half periodCount, half spacePerPeriod, int snapMode ){
switch( snapMode ){
case DASH_SNAP_OFF:
return periodCount;
case DASH_SNAP_TILING:
case DASH_SNAP_ENDTOEND_PERIODIC:
return max( 1, round( periodCount ) );
case DASH_SNAP_ENDTOEND:
return max( 1, round( periodCount + spacePerPeriod ) ) - spacePerPeriod;
default:
return 0;
}
}
inline DashCoordinates GetDashCoordinates( DashConfig dash, float dist, float distTotal, half thickness, half pxPerMeter ) {
DashCoordinates dashCoords;
// dist and dist total are both in meters, so we need to convert them to match if we're using relative coords
if( dash.space == DASH_SPACE_RELATIVE ) {
dist = MetersToOtherSpace( dist, pxPerMeter, dash.thicknessSpace );
distTotal = MetersToOtherSpace( distTotal, pxPerMeter, dash.thicknessSpace );
thickness = MetersToOtherSpace( thickness, pxPerMeter, dash.thicknessSpace );
}
// first, convert to dash count, to leave non-normalized space land asap because we hate it
bool fixedCount = dash.space == DASH_SPACE_FIXED_COUNT;
float periodCount, spacePerPeriod;
if( fixedCount ){
spacePerPeriod = dash.spacing;
periodCount = DashSnap( dash.size, spacePerPeriod, dash.snap );
} else {
float rawPeriod = (dash.size + dash.spacing);
spacePerPeriod = dash.spacing / rawPeriod;
periodCount = DashSnap( distTotal / rawPeriod, spacePerPeriod, dash.snap );
}
float dashPerPeriod = 1-spacePerPeriod;
if( dash.snap == DASH_SNAP_ENDTOEND_PERIODIC )
dash.offset -= dashPerPeriod*0.5; // offset by half a dash distance to center
dashCoords.spacePerPeriod = spacePerPeriod;
dashCoords.thicknessPerPeriod = (thickness*periodCount) / (distTotal);
float t = dist / distTotal; // normalized longitudinal coord
dashCoords.coord = t * periodCount - dash.offset - dashPerPeriod/2;
return dashCoords;
}
inline bool IsDashed() {
return PROP( _DashSize ) > VERY_SMOL;
}