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

234 lines
8.6 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(half4, _Color)
PROP_DEF(half, _Thickness)
PROP_DEF(int, _ThicknessSpace)
PROP_DEF(int, _Alignment)
UNITY_INSTANCING_BUFFER_END(Props)
#define ALIGNMENT_FLAT 0
#define ALIGNMENT_BILLBOARD 1
#define IP_nrmCoordLat intp0.x
#define IP_nrmCoordLong intp0.y
#define IP_radius intp0.z
#define IP_pxCoverage intp0.w
struct VertexInput {
UNITY_VERTEX_INPUT_INSTANCE_ID
float4 vertex : POSITION; // current
float4 uv0 : TEXCOORD0; // uvs (XY) endpoint (Z) thickness (W)
float3 uv1 : TEXCOORD1; // prev
float3 uv2 : TEXCOORD2; // next
float4 color : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
half4 color : TEXCOORD0;
half4 intp0 : TEXCOORD1;
#if defined(IS_JOIN_MESH) && defined(JOIN_ROUND)
float3 worldPos : TEXCOORD2;
float3 origin : TEXCOORD3;
#endif
UNITY_FOG_COORDS(4)
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
void GetSimpleOffset( out float3 dir, out float len, float3 aNormal, float3 bNormal, float radius ){
float dotVal = dot(aNormal, bNormal);
if (dotVal < -0.99) {
dir = float3(0, 0, 0);
len = 0;
} else {
dir = normalize( aNormal + bNormal );
len = radius;
}
}
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
// alignment
int alignment = PROP(_Alignment);
// points
float3 ptWorld = 0;
float3 pt = 0;
float3 ptPrev = 0;
float3 ptNext = 0;
half3 dirToCam = 0;
switch( alignment ){
case ALIGNMENT_FLAT: {
// local space
pt = float3( v.vertex.xy, 0 );
ptWorld = LocalToWorldPos( pt );
ptPrev = float3( v.uv1.xy, 0 );
ptNext = float3( v.uv2.xy, 0 );
break;
}
case ALIGNMENT_BILLBOARD: {
pt = LocalToWorldPos( v.vertex.xyz );
ptWorld = pt;
ptPrev = LocalToWorldPos( v.uv1 );
ptNext = LocalToWorldPos( v.uv2 );
dirToCam = DirectionToNearPlanePos( pt );
break;
}
}
// tangents & normals
half3 tangentPrev = normalize( pt - ptPrev );
half3 tangentNext = normalize( ptNext - pt );
half3 normalPrev = 0;
half3 normalNext = 0;
switch( alignment ){
case ALIGNMENT_FLAT: {
// local space
normalPrev = half3( tangentPrev.y, -tangentPrev.x, 0 );
normalNext = half3( tangentNext.y, -tangentNext.x, 0 );
break;
}
case ALIGNMENT_BILLBOARD: {
// world space
normalPrev = normalize(cross( dirToCam, tangentPrev ));
normalNext = normalize(cross( dirToCam, tangentNext ));
break;
}
}
int scaleMode = PROP(_ScaleMode);
half uniformScale = GetUniformScale();
half scaleThickness = scaleMode == SCALE_MODE_UNIFORM ? 1 : 1.0/uniformScale;
// thickness stuff
half thickness = PROP(_Thickness) * v.uv0.w * scaleThickness; // global thickness * per-point thickness * transform scaling
// radius calc
int thicknessSpace = PROP(_ThicknessSpace);
LineWidthData widthData = GetScreenSpaceWidthData( ptWorld, CAM_RIGHT, thickness, thicknessSpace );
o.IP_pxCoverage = widthData.thicknessPixelsTarget / scaleThickness; // hack: this is a lil weird honestly - I think we're mixing local vs world somehere before this
half vertexRadius = widthData.thicknessMeters * 0.5;
#if LOCAL_ANTI_ALIASING_QUALITY > 0
half paddingWorldSpace = (0.5 * AA_PADDING_PX / widthData.pxPerMeter);
bool isEndpoint = abs(v.uv0.z) > 0;
half endpointExtrude = isEndpoint ? paddingWorldSpace : 0;
#else
half endpointExtrude = 0;
#endif
o.IP_radius = vertexRadius / widthData.aaPaddingScale; // actual radius of the visuals
// miter point calc
half turnDirection = sign(dot( tangentPrev, normalNext ));
#if defined(IS_JOIN_MESH) // only draw joins
half tSide = (-turnDirection*v.uv0.y + 1)/2;
half tExtVsMiter = (v.uv0.x + 1)/2;
half3 normalPrevFlip = turnDirection*normalPrev;
half3 normalNextFlip = turnDirection*normalNext;
half3 sideExtrudeDir = lerp( normalPrevFlip, normalNextFlip, tSide );
half3 centerNormalDir = normalize( normalPrevFlip + normalNextFlip );
half3 miterDir;
half miterLength;
GetMiterOffset( /*out*/ miterDir, /*out*/ miterLength, sideExtrudeDir, centerNormalDir, vertexRadius );
half3 miterOffset = miterDir * miterLength;
half3 vertOffset = lerp( miterOffset, sideExtrudeDir * vertexRadius, tExtVsMiter ) * abs(v.uv0.x);
float3 vertPos = pt + vertOffset;
#else // only draw line segments
half3 miterDir;
half miterLength;
// all these boys use proper miters
#if defined(JOIN_MITER) || defined(JOIN_ROUND) || defined(JOIN_BEVEL)
GetMiterOffset( /*out*/ miterDir, /*out*/ miterLength, normalPrev, normalNext, vertexRadius );
// make sure miter length doesn't overshoot line length
// or not you know because it turns out~ this is more complicated than this
// (because it changes thickness) so, uh, nevermind for now
// miterLength = lerp(miterLength, min(miterLength, min(length(pt - ptPrev),length(ptNext - pt)) ),(-v.uv0.x*turnDirection + 1)/2 );
//float3 midpt = (ptNext + ptPrev)/2;
//miterLength = min(miterLength, min( distance( pt, ptNext ), distance( pt, ptPrev ) ) );
#else
// simple joins
GetSimpleOffset( /*out*/ miterDir, /*out*/ miterLength, normalPrev, normalNext, vertexRadius );
#endif
half3 scaledMiterNormal = miterDir * miterLength;
#if defined(JOIN_ROUND) || defined(JOIN_BEVEL)
half3 extrude = lerp(normalPrev, normalNext, (v.uv0.y + 1)/2 ) * vertexRadius;
extrude = lerp( extrude, scaledMiterNormal, (-v.uv0.x*turnDirection + 1)/2 );
float3 vertPos = pt + extrude * v.uv0.x;
#else
float3 vertPos = pt + scaledMiterNormal * v.uv0.x; // float3(v.tangent.xy * v.uv0.x * vertexRadius,0);
#endif
#if LOCAL_ANTI_ALIASING_QUALITY > 0
vertPos += tangentPrev * endpointExtrude * v.uv0.z; // is 0 if not an endpoint
half distToPrev = distance(pt, ptPrev);
v.uv0.z *= (distToPrev + endpointExtrude)/(distToPrev); // ratio for extrude dist compensation
#endif
#endif
#if defined(IS_JOIN_MESH) && defined(JOIN_ROUND)
o.worldPos = vertPos;
o.origin = pt;
#endif
o.color = v.color * PROP(_Color);
// todo: this causes *bad things* with anti-aliasing
// it's complicated - this doesn't work due to how barycentric interpolation skews coordinates.
// just scaling the UVs overscales those skew regions leading to dents in the AA region
o.IP_nrmCoordLat = v.uv0.x * widthData.aaPaddingScale; // scale compensate for fading
o.IP_nrmCoordLong = v.uv0.z;
//float depth = unity_ObjectToWorld[2][3];
switch( alignment ){
case ALIGNMENT_FLAT: {
o.pos = LocalToClipPos( float4( vertPos, 1 ) );
break;
}
case ALIGNMENT_BILLBOARD: {
o.pos = WorldToClipPos( float4( vertPos, 1 ) );
break;
}
}
UNITY_TRANSFER_FOG(o,o.pos);
return o;
}
FRAG_OUTPUT_V4 frag( VertexOutput i ) : SV_Target {
UNITY_SETUP_INSTANCE_ID(i);
// debug padding
// float2 paddingDebug = abs(float2( i.uv0.x, i.uv0.z )) >= 1 ? 1 : 0;
//return ShapesOutput( float4(uv,0,1), shape_mask );
half shape_mask = 1;
// Round joins
#if defined(IS_JOIN_MESH) && defined(JOIN_ROUND)
shape_mask = SdfToMask( distance( i.worldPos, i.origin ) / i.IP_radius, 1 );
#endif
// used for line segments and bevel joins
#if LOCAL_ANTI_ALIASING_QUALITY > 0 && ( defined(IS_JOIN_MESH) == false || (defined(IS_JOIN_MESH) && defined(JOIN_BEVEL)) )
half maskEdges = GetLineLocalAA( i.IP_nrmCoordLat, i.IP_pxCoverage );
half maskEdgesCap = GetLineLocalAA( i.IP_nrmCoordLong, i.IP_pxCoverage );
shape_mask = min( shape_mask, min( maskEdges, maskEdgesCap ) );
#endif
shape_mask *= saturate( i.IP_pxCoverage );
return SHAPES_OUTPUT( i.color, shape_mask, i );
}