Last active
September 25, 2024 06:24
-
-
Save jjrv/2ed4c69aba7b571cd4ca91ce705779b2 to your computer and use it in GitHub Desktop.
Fragment shader for variable thickness line segment with gradient fill and rounded cap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Copyright 2024- Juha Järvi | |
Permission to use, copy, modify, and/or distribute this software for any | |
purpose with or without fee is hereby granted. | |
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
PERFORMANCE OF THIS SOFTWARE. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Perp dot product / cross product magnitude / 2x2 matrix determinant. */ | |
float perpdot(vec2 a, vec2 b) { | |
return a.x * b.y - a.y * b.x; | |
} | |
/** SDF of the convex hull of two circles / uneven capsule, | |
* for extruding a line segment with linearly changing thickness. | |
* | |
* @param d B - A where A and B are vec3(x, y, radius) of each endpoint. | |
* @param f gl_FragCoord.xy - A. | |
* | |
* @return Signed distance to convex hull. */ | |
float sdCirclePairHull(vec3 d, vec3 f) { | |
// Line segment length before extrusion, squared. | |
float d2 = dot(d.xy, d.xy); | |
// Length of external common tangent of circles around endpoints. | |
float tangentLen = sqrt(max(d2 - d.z * d.z, 0.0)); | |
// Right triangle where base is the tangent line and hypotenuse is the distance between unextruded endpoints. | |
vec2 tangent = vec2(tangentLen, d.z); | |
// Rotate and scale fragcoord to a space where: | |
// - A is at (0, 0). | |
// - B is at (1, 0). | |
// - x axis is aligned with the unextruded line segment. | |
// - y axis is the distance from the line segment divided by its length | |
// (magnitude, to handle distance to the closest tangent line next). | |
vec2 aligned = vec2( | |
dot(f.xy, d.xy), | |
abs(perpdot(f.xy, d.xy)) | |
) / d2; | |
// Offset of fragcoord along both tangent line segments from edge of circle around point A, to circle around point B. | |
// If there is no tangent line, choose the supposed endpoint with the larger radius. | |
float t = clamp(tangent.x == 0.0 ? sign(d.z) : dot(tangent, aligned) / tangent.x, 0.0, 1.0); | |
if(fract(t) == 0.0) { | |
// Projection of fragcoord to either tangent line segment is at an endpoint. | |
return length(d.xy * t - f.xy) - d.z * t; | |
} | |
// Signed distance from fragcoord to point projected on tangent line. | |
return perpdot(tangent, aligned); | |
} | |
/** Like for 2D canvas createRadialGradient, calculate the offset where a circle sweep | |
* from A to B last intersects a point. | |
* | |
* @param d B - A where A and B are vec3(x, y, radius) of each sweep endpoint. | |
* @param f gl_FragCoord.xy - A. | |
* | |
* @return Offset 0-1 where a circle sweep from A to B last intersects fragcoord, | |
* or -1 if fragcoord is outside the sweep (sdHull return value is > radius of A). */ | |
float radialGradientOffset(vec3 d, vec3 f) { | |
// Proportional to distance between endpoint circles along the line segment. | |
// Zero if circles touch on the line segment. | |
float A = 2.0 * dot(d, vec3(d.xy, -d.z)); | |
float B = 2.0 * dot(d, -f); | |
float C = 2.0 * dot(f, vec3(f.xy, -f.z)); | |
// Offset along the line at the center of a circle that touches this point. | |
float t = -1.0; | |
const float epsilon = 1e-6; | |
if(abs(A) < epsilon) { | |
if(d.z * direction > 0.0 && abs(B) > epsilon) t = -0.5 * C / B; | |
} else { | |
// If negative, we're on either side outside the circle sweep. | |
float discriminant = B * B - A * C; | |
if(discriminant >= 0.0) t = (-B - sqrt(discriminant) * direction) / A; | |
} | |
return t; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment