Skip to content

fix(element): stabilize MSDF text anti-aliasing at small sizes#8990

Merged
willeastcott merged 2 commits into
mainfrom
fix/msdf-text-aa-shimmer
Jun 30, 2026
Merged

fix(element): stabilize MSDF text anti-aliasing at small sizes#8990
willeastcott merged 2 commits into
mainfrom
fix/msdf-text-aa-shimmer

Conversation

@willeastcott

Copy link
Copy Markdown
Contributor

Problem

Small MSDF text rendered via an ElementComponent shimmers and looks rough since 2.20: as the text translates, thin 1px strokes (l, i, 1) flicker on and off and edges crawl. Regression from #8935. (#8984)

2.20 (shimmers) This PR
aa = fwidth(median(sample)) — content-derived, noisy under minification geometry-derived screenPxRange, stable

Cause

#8935 made two independent changes. The coverage threshold (edge = 0.5 - 0.5 * font_sdfIntensity, which fixed the #2948 crossbar erosion) is correct and kept. The regression is the other change — deriving the AA width from the distance-field gradient, aa = fwidth(median(sample)).

MSDF atlases can't be mipmapped (averaging the channels breaks the median), so under minification the field is undersampled and the sampled median is noisy. Its screen-space gradient therefore changes frame-to-frame as the glyph moves sub-pixel, so the AA width breathes (→ shimmer), and where the gradient momentarily collapses the edge goes razor-sharp (→ thin strokes snap on/off). Pre-2.20 the AA width came from the uv magnification only, which is constant across a glyph and independent of sub-pixel position — stable.

Fix

Compute the transition width with the msdfgen-standard screenPxRange(): derived from fwidth(vUv0) (both axes) and the atlas spread, floored at 1px. It depends only on geometry, so it is stable under motion and minification, and using both uv axes also covers the diagonal/horizontal edges that motivated the gradient approach in #8935. The true-edge threshold is unchanged, so the #2948 fix and the rendered weight are preserved — this PR only changes AA stability.

Re-adds the font_pxrange uniform removed by #8935 (atlas size now comes from textureSize/textureDimensions, so font_textureWidth stays gone). GLSL and WGSL kept in parity. msdfPS bumped to chunk version 2.21.

Testing

  • WebGL2, live A/B against engine source on the SDF Font quality regression. #8984 repro (Roboto, fontSize 6, DPR 1.75 ≈ 3 atlas-texels/screen-px): total text coverage over a sub-pixel translation cycle fluctuates ±1.4% with the fix vs ±5.7% on 2.20 (≈4× less). Coverage is conserved under pure translation, so its fluctuation is the shimmer. No shader-compile errors; text renders correctly.
  • Numerical simulation of the exact shader math on the real atlas, isolating a single l stem: the 2.20 AA flickered ≈7× more than pre-2.20, the stem peak snapping between ~0.5 and a hard 1.0; the fix is flat.
  • npm test (incl. text-element, 90 passing) and lint clean.

Fixes #8984

…e field gradient

#8935 switched the coverage anti-aliasing to fwidth(median(sample)). On
minified (small) text the atlas is undersampled, so that gradient is noisy
and breathes as the text translates - small text shimmers and thin strokes
(l, i, 1) flicker on and off (#8984).

Derive the transition width from the uv magnification (both axes) and the
atlas spread instead - the msdfgen screenPxRange approach - which depends
only on geometry, so it is stable under motion and minification, floored at
1px. Keep the true-edge threshold (edge = 0.5 - 0.5*intensity) from #8935 so
the #2948 crossbar fix and the rendered weight are unchanged.

Re-adds the font_pxrange uniform removed by #8935 (atlas size comes from
textureSize/textureDimensions); bumps the msdfPS chunk version to 2.21.

Fixes #8984

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2270.0 KB (+0.8 KB, +0.03%) 584.3 KB (+0.2 KB, +0.03%) 454.0 KB (+0.1 KB, +0.02%)
playcanvas.min.mjs 2267.4 KB (+0.8 KB, +0.03%) 583.4 KB (+0.2 KB, +0.03%) 453.6 KB (+0.2 KB, +0.04%)

@willeastcott willeastcott merged commit ce282c8 into main Jun 30, 2026
10 checks passed
@willeastcott willeastcott deleted the fix/msdf-text-aa-shimmer branch June 30, 2026 13:50
willeastcott added a commit that referenced this pull request Jun 30, 2026
…e field gradient (#8990)

#8935 switched the coverage anti-aliasing to fwidth(median(sample)). On
minified (small) text the atlas is undersampled, so that gradient is noisy
and breathes as the text translates - small text shimmers and thin strokes
(l, i, 1) flicker on and off (#8984).

Derive the transition width from the uv magnification (both axes) and the
atlas spread instead - the msdfgen screenPxRange approach - which depends
only on geometry, so it is stable under motion and minification, floored at
1px. Keep the true-edge threshold (edge = 0.5 - 0.5*intensity) from #8935 so
the #2948 crossbar fix and the rendered weight are unchanged.

Re-adds the font_pxrange uniform removed by #8935 (atlas size comes from
textureSize/textureDimensions). The msdfPS chunk version is left at 2.20 on
this release line.

Fixes #8984

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
willeastcott added a commit that referenced this pull request Jun 30, 2026
#8990 re-added the font_pxrange uniform and bumped msdfPS to 2.21, but that
change is additive: a 2.20-era override that doesn't read font_pxrange just
ignores the extra uniform and keeps working. The only overrides that break
are pre-2.20 ones (they read the removed font_textureWidth), and those are
already flagged by the existing 2.20 marker. 2.20 stays the last breaking
change to the chunk, and this keeps main and release-2.20 consistent.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui UI related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SDF Font quality regression.

1 participant