#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); } }