163 lines
4.4 KiB
GLSL
163 lines
4.4 KiB
GLSL
#version 460
|
|
#extension GL_GOOGLE_include_directive : require
|
|
#extension GL_EXT_nonuniform_qualifier : enable
|
|
|
|
#include "bindless.glsl"
|
|
#include "shape2d.glsl"
|
|
|
|
layout (location = 0) in vec2 inLocalPos;
|
|
layout (location = 1) in vec2 inQuadPos;
|
|
layout (location = 2) flat in uint inShapeIndex;
|
|
|
|
layout (location = 0) out vec4 outFragColor;
|
|
|
|
// FreeType stores the edge at 128, inside is brighter
|
|
const float TEXT_SDF_EDGE = 128.0 / 255.0;
|
|
|
|
float roundedRectDistance(vec2 position, vec2 halfSize, float cornerRadius)
|
|
{
|
|
cornerRadius = min(cornerRadius, min(halfSize.x, halfSize.y));
|
|
|
|
vec2 cornerOffset = abs(position) - halfSize + cornerRadius;
|
|
float outsideDistance = length(max(cornerOffset, 0.0));
|
|
float insideDistance = min(max(cornerOffset.x, cornerOffset.y), 0.0);
|
|
|
|
return outsideDistance + insideDistance - cornerRadius;
|
|
}
|
|
|
|
// Approximate, but exact on the boundary which is what the edge and outline need
|
|
float ellipseDistance(vec2 position, vec2 radii)
|
|
{
|
|
radii = max(radii, vec2(1e-4));
|
|
|
|
float normalizedLength = length(position / radii);
|
|
if (normalizedLength < 1e-4) {
|
|
return -min(radii.x, radii.y);
|
|
}
|
|
|
|
float gradientLength = length(position / (radii * radii));
|
|
return normalizedLength * (normalizedLength - 1.0) / gradientLength;
|
|
}
|
|
|
|
vec2 closestPointOnSegmentOffset(vec2 position, vec2 segmentStart, vec2 segmentEnd)
|
|
{
|
|
vec2 segment = segmentEnd - segmentStart;
|
|
vec2 toPosition = position - segmentStart;
|
|
|
|
float along = clamp(
|
|
dot(toPosition, segment) / max(dot(segment, segment), 1e-8),
|
|
0.0, 1.0
|
|
);
|
|
|
|
return toPosition - segment * along;
|
|
}
|
|
|
|
float edgeSide(vec2 position, vec2 edgeStart, vec2 edgeEnd)
|
|
{
|
|
vec2 edge = edgeEnd - edgeStart;
|
|
vec2 toPosition = position - edgeStart;
|
|
return toPosition.x * edge.y - toPosition.y * edge.x;
|
|
}
|
|
|
|
float triangleDistance(vec2 position, vec2 a, vec2 b, vec2 c)
|
|
{
|
|
vec2 offsetAB = closestPointOnSegmentOffset(position, a, b);
|
|
vec2 offsetBC = closestPointOnSegmentOffset(position, b, c);
|
|
vec2 offsetCA = closestPointOnSegmentOffset(position, c, a);
|
|
|
|
float closestDistanceSquared = min(
|
|
dot(offsetAB, offsetAB),
|
|
min(dot(offsetBC, offsetBC), dot(offsetCA, offsetCA))
|
|
);
|
|
|
|
float winding = sign(edgeSide(c, a, b));
|
|
bool isInside =
|
|
winding * edgeSide(position, a, b) > 0.0 &&
|
|
winding * edgeSide(position, b, c) > 0.0 &&
|
|
winding * edgeSide(position, c, a) > 0.0;
|
|
|
|
float edgeDistance = sqrt(closestDistanceSquared);
|
|
return isInside ? -edgeDistance : edgeDistance;
|
|
}
|
|
|
|
float edgeCoverage(float signedDistance)
|
|
{
|
|
float pixelWidth = max(fwidth(signedDistance), 1e-4);
|
|
return clamp(0.5 - signedDistance / pixelWidth, 0.0, 1.0);
|
|
}
|
|
|
|
vec2 shapeUv(Shape2D shape)
|
|
{
|
|
return mix(
|
|
shape.uvRect.xy,
|
|
shape.uvRect.zw,
|
|
clamp(inQuadPos, 0.0, 1.0)
|
|
);
|
|
}
|
|
|
|
vec4 shadeText(Shape2D shape)
|
|
{
|
|
float sdf = sampleTexture2D(
|
|
shape.textureId, shape.samplerId, shapeUv(shape)
|
|
).r;
|
|
|
|
float signedDistance = (TEXT_SDF_EDGE - sdf) * shape.p0.x;
|
|
float fillCoverage = edgeCoverage(signedDistance);
|
|
|
|
if (shape.outline <= 0.0) {
|
|
return vec4(
|
|
shape.color.rgb,
|
|
shape.color.a * fillCoverage
|
|
);
|
|
}
|
|
|
|
vec4 outlineColor = vec4(shape.p1, shape.p2);
|
|
float outlineCoverage = edgeCoverage(signedDistance - shape.outline);
|
|
|
|
vec4 result = mix(outlineColor, shape.color, fillCoverage);
|
|
result.a *= outlineCoverage;
|
|
|
|
return result;
|
|
}
|
|
|
|
float shapeDistance(Shape2D shape)
|
|
{
|
|
if (shape.type == SHAPE_ELLIPSE) {
|
|
return ellipseDistance(inLocalPos, shape.halfSize);
|
|
}
|
|
if (shape.type == SHAPE_TRIANGLE) {
|
|
return triangleDistance(inLocalPos, shape.p0, shape.p1, shape.p2);
|
|
}
|
|
return roundedRectDistance(inLocalPos, shape.halfSize, shape.cornerRadius);
|
|
}
|
|
|
|
vec4 shadeShape(Shape2D shape)
|
|
{
|
|
float signedDistance = shapeDistance(shape);
|
|
|
|
if (shape.outline > 0.0) {
|
|
float halfOutline = shape.outline * 0.5;
|
|
signedDistance = abs(signedDistance + halfOutline) - halfOutline;
|
|
}
|
|
|
|
vec4 textureColor = sampleTexture2D(
|
|
shape.textureId, shape.samplerId, shapeUv(shape)
|
|
);
|
|
|
|
vec4 result = textureColor * shape.color;
|
|
result.a *= edgeCoverage(signedDistance);
|
|
|
|
return result;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
Shape2D shape = pcs.shapeBuffer.shapes[inShapeIndex];
|
|
|
|
if (shape.type == SHAPE_TEXT) {
|
|
outFragColor = shadeText(shape);
|
|
} else {
|
|
outFragColor = shadeShape(shape);
|
|
}
|
|
}
|