Skip to main content

MSDF_FRAGMENT_SOURCE

Constant MSDF_FRAGMENT_SOURCE 

Source
pub const MSDF_FRAGMENT_SOURCE: &str = "// MSDF (Multi-channel Signed Distance Field) text fragment shader.\n//\n// This is a standalone fragment shader intended for use with the custom shader system\n// (ShaderStore). The standard preamble (camera, texture, lighting, vertex shader) and\n// ShaderParams uniform are prepended automatically by ShaderStore::create().\n//\n// The fragment shader computes median(R, G, B) from the MSDF atlas, then uses\n// smoothstep for crisp anti-aliased edges at any scale.\n//\n// Uniform slots (shader_params.values[]):\n//   [0]: [distance_range, font_size_px, screen_px_range, _pad]\n//   [1]: outline [width, r, g, b]\n//   [2]: outline [a, _, _, _]\n//   [3]: shadow [offset_x, offset_y, softness, _]\n//   [4]: shadow [r, g, b, a]\n//\n// Supports:\n// - Resolution-independent text rendering\n// - Configurable outline (width + color)\n// - Configurable shadow (offset + color + softness)\n\n/// Compute median of three values (the core MSDF operation).\nfn median3(r: f32, g: f32, b: f32) -> f32 {\n    return max(min(r, g), min(max(r, g), b));\n}\n\n/// Sample the MSDF atlas and return the screen-space distance.\nfn msdf_sample_distance(uv: vec2<f32>, screen_px_range: f32) -> f32 {\n    let msd = textureSample(t_diffuse, s_diffuse, uv);\n    let sd = median3(msd.r, msd.g, msd.b);\n    return (sd - 0.5) * screen_px_range;\n}\n\n@fragment\nfn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {\n    let screen_px_range = shader_params.values[0].z;\n\n    // --- Shadow pass ---\n    let shadow_offset = shader_params.values[3].xy;\n    let shadow_softness = shader_params.values[3].z;\n    let shadow_color = shader_params.values[4];\n    var shadow_alpha = 0.0;\n\n    if (shadow_color.a > 0.0) {\n        // Compute texel size from atlas dimensions for shadow offset\n        let tex_size = vec2<f32>(textureDimensions(t_diffuse, 0));\n        let shadow_uv = in.tex_coords + shadow_offset / tex_size;\n        let shadow_dist = msdf_sample_distance(shadow_uv, screen_px_range);\n        let softness_factor = max(shadow_softness, 1.0);\n        shadow_alpha = smoothstep(-softness_factor, softness_factor, shadow_dist) * shadow_color.a;\n    }\n\n    // --- Outline pass ---\n    let outline_width = shader_params.values[1].x;\n    let outline_rgb = shader_params.values[1].yzw;\n    let outline_a = shader_params.values[2].x;\n\n    let dist = msdf_sample_distance(in.tex_coords, screen_px_range);\n\n    var outline_alpha = 0.0;\n    if (outline_width > 0.0 && outline_a > 0.0) {\n        // Outline extends outward from the glyph edge\n        outline_alpha = smoothstep(-outline_width - 0.5, -outline_width + 0.5, dist) * outline_a;\n    }\n\n    // --- Fill pass ---\n    let fill_alpha = smoothstep(-0.5, 0.5, dist);\n\n    // Composite: shadow behind outline behind fill\n    var color = vec4<f32>(0.0);\n\n    // Shadow layer\n    if (shadow_alpha > 0.0) {\n        color = vec4<f32>(shadow_color.rgb, shadow_alpha);\n    }\n\n    // Outline layer (over shadow)\n    if (outline_alpha > 0.0) {\n        let oa = outline_alpha * (1.0 - fill_alpha); // Only show outline where fill isn\'t\n        color = vec4<f32>(\n            mix(color.rgb, outline_rgb, oa),\n            max(color.a, oa),\n        );\n    }\n\n    // Fill layer (over outline and shadow)\n    let fill_color = in.tint;\n    if (fill_alpha > 0.0) {\n        let fa = fill_alpha * fill_color.a;\n        color = vec4<f32>(\n            mix(color.rgb, fill_color.rgb, fa),\n            max(color.a, fa),\n        );\n    }\n\n    // Apply lighting\n    var light_color = lighting.ambient;\n    for (var i = 0u; i < lighting.light_count; i = i + 1u) {\n        let light = lighting.lights[i];\n        let light_pos = light.pos_radius.xy;\n        let radius = light.pos_radius.z;\n        let lcolor = light.color_intensity.rgb;\n        let intensity = light.color_intensity.a;\n\n        let d = length(in.world_position - light_pos);\n        let atten = smoothstep(radius, 0.0, d) * intensity;\n        light_color = light_color + lcolor * atten;\n    }\n    light_color = clamp(light_color, vec3<f32>(0.0), vec3<f32>(1.0));\n\n    return vec4<f32>(color.rgb * light_color, color.a);\n}\n";
Expand description

The MSDF fragment shader source (used with ShaderStore::create). This is the fragment portion only; the standard vertex preamble and ShaderParams uniform are prepended by the ShaderStore automatically.