150 lines
5.2 KiB
HLSL
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;
|
|
} |