From 88587fb6a4f3ad036cf073cb73441840353a2449 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 20:24:03 +0100 Subject: [PATCH] Add utility function to `GUICommon.hlsl` for MSDF fonts sampling in shaders #3944 --- Content/Shaders/GUI.flax | 4 +-- Source/Engine/Render2D/FontAsset.h | 6 ++--- Source/Shaders/GUI.shader | 20 +++----------- Source/Shaders/GUICommon.hlsl | 43 ++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax index 484f09f46..3e0ccc18f 100644 --- a/Content/Shaders/GUI.flax +++ b/Content/Shaders/GUI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:551f893b015cd364911270816607651917545c83d28259c918018ff3ffc15937 -size 5333 +oid sha256:b816ba638bc37b8a8288e4c397ff5b708153057650fa6b626a109ba42d189694 +size 4721 diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index c9966c68b..56cc3d655 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -77,7 +77,7 @@ API_ENUM() enum class FontRasterMode : byte Bitmap, /// - /// Use the MSDF generator to render font atlases. Need to be rendered with a compatible material. + /// Use the Multi-channel Signed Distance Field (MSDF) generator to render font atlases. Need to be rendered with a compatible material. /// MSDF, }; @@ -92,7 +92,7 @@ API_STRUCT() struct FontOptions DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions); /// - /// The hinting. + /// The font hinting used when rendering characters. /// API_FIELD() FontHinting Hinting; @@ -102,7 +102,7 @@ API_STRUCT() struct FontOptions API_FIELD() FontFlags Flags; /// - /// The rasterization mode. + /// The font rasterization mode. /// API_FIELD() FontRasterMode RasterMode; }; diff --git a/Source/Shaders/GUI.shader b/Source/Shaders/GUI.shader index b09a3474d..a8f9452e9 100644 --- a/Source/Shaders/GUI.shader +++ b/Source/Shaders/GUI.shader @@ -92,7 +92,7 @@ float4 PS_Font(VS2PS input) : SV_Target0 PerformClipping(input); float4 color = input.Color; - color.a *= Image.Sample(SamplerLinearClamp, input.TexCoord).r; + color.a *= SampleFont(Image, input.TexCoord); return color; } @@ -102,22 +102,8 @@ float4 PS_FontMSDF(VS2PS input) : SV_Target0 PerformClipping(input); float4 color = input.Color; - float3 msd = Image.Sample(SamplerLinearClamp, input.TexCoord).rgb; - float sd = max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); - - uint width, height; - Image.GetDimensions(width, height); - float pxRange = 4.0f; // Must match C++ code - float unitRange = float2(pxRange, pxRange) / float2(width, height); - - float2 dx = ddx(input.TexCoord); - float2 dy = ddy(input.TexCoord); - float2 screenTexSize = rsqrt(dx * dx + dy * dy); - float screenPxRange = max(0.5f * dot(screenTexSize, unitRange), 1.0f); - float screenPxDist = screenPxRange * (sd - 0.5f); - - float opacity = saturate(screenPxDist + 0.5f); - return float4(color.rgb, opacity); + color.a *= SampleFontMSDF(Image, input.TexCoord); + return color; } float4 GetSample(float weight, float offset, float2 uv) diff --git a/Source/Shaders/GUICommon.hlsl b/Source/Shaders/GUICommon.hlsl index 14c8b1327..67f9daac4 100644 --- a/Source/Shaders/GUICommon.hlsl +++ b/Source/Shaders/GUICommon.hlsl @@ -54,4 +54,47 @@ void PerformClipping(VS2PS input) PerformClipping(input.ClipOriginAndPos.xy, input.ClipOriginAndPos.zw, input.ClipExtents); } +float SampleFont(Texture2D font, float2 uv) +{ + return font.Sample(SamplerLinearClamp, uv).r; +} + +float GetFontMSDFMedian(Texture2D font, float2 uv) +{ + float4 msd = font.Sample(SamplerLinearClamp, uv); + return max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); +} + +float GetFontMSDFPixelRange(Texture2D font, float2 uv) +{ + uint width, height; + font.GetDimensions(width, height); + float pxRange = 4.0f; // Must match C++ code + float unitRange = float2(pxRange, pxRange) / float2(width, height); + + float2 dx = ddx(uv); + float2 dy = ddy(uv); + float2 screenTexSize = rsqrt(dx * dx + dy * dy); + return max(0.5f * dot(screenTexSize, unitRange), 1.0f); +} + +float SampleFontMSDF(Texture2D font, float2 uv) +{ + float sd = GetFontMSDFMedian(font, uv); + float screenPxRange = GetFontMSDFPixelRange(font, uv); + float screenPxDist = screenPxRange * (sd - 0.5f); + return saturate(screenPxDist + 0.5f); +} + +float SampleFontMSDFOutline(Texture2D font, float2 uv, float thickness) +{ + float4 msd = font.Sample(SamplerLinearClamp, uv); + float sd = max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); + float screenPxRange = GetFontMSDFPixelRange(font, uv); + float thick = clamp(thickness, 0.0, screenPxRange * 0.5 - 1.0) / screenPxRange; + float outline = saturate((min(sd, msd.a) - 0.5 + thick) * screenPxRange + 0.5); + outline *= 1 - saturate(screenPxRange * (sd - 0.5f) + 0.5f); + return outline; +} + #endif