Files
prosperon/shaders/msl/text_msdf.frag.msl
2026-02-25 16:58:06 -06:00

65 lines
2.1 KiB
Plaintext

#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float2 uv;
float4 color;
};
struct Uniforms {
float outline_width; // Outline width in normalized SDF units (0.0 = no outline, 0.1-0.3 typical)
float sharpness; // Sharpness multiplier (1.0 = normal, higher = sharper)
float2 _pad; // Padding for alignment
float4 outline_color; // Outline color RGBA
};
// Median of three values - used to combine MSDF channels
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> tex [[texture(0)]],
sampler smp [[sampler(0)]],
constant Uniforms &u [[buffer(0)]]) {
// Sample RGB channels from MSDF texture
float3 msdf = tex.sample(smp, in.uv).rgb;
// Compute signed distance from the median of the three channels
float dist = median(msdf.r, msdf.g, msdf.b);
// Edge is at 0.5 (where distance = 0 in the original SDF)
float edge = 0.5;
// Calculate anti-aliasing width based on screen-space derivatives
// MSDF typically needs tighter AA than single-channel SDF
float aa = fwidth(dist);
float sharpness = max(u.sharpness, 0.1);
aa = aa / sharpness;
// Fill coverage: inside the glyph
float fill = smoothstep(edge - aa, edge + aa, dist);
// Outline coverage: extends outward from the edge
float outline_coverage = 0.0;
if (u.outline_width > 0.0) {
float outline_edge = edge - u.outline_width;
outline_coverage = smoothstep(outline_edge - aa, outline_edge + aa, dist);
}
// Total coverage is the union of fill and outline
float total_coverage = max(fill, outline_coverage);
// Blend colors: outline where not filled, fill color where filled
float3 rgb = in.color.rgb;
if (u.outline_width > 0.0) {
rgb = mix(u.outline_color.rgb, in.color.rgb, fill);
}
// Final alpha combines coverage with vertex alpha
float a = total_coverage * in.color.a;
return float4(rgb, a);
}