ESC
科技 9 分钟阅读

SDF、MSDF 与 Slug 三种方案对比:GPU 文字渲染技术解析

文章系统对比三种 GPU 文字渲染方案:Valve 的 SDF 借助有向距离场实现可缩放清晰文字,但会磨圆尖角;Chlumsky 的 MSDF 用三通道中位数技巧保留尖角,仍需烘焙图集;Eric Lengyel 的 Slug 则完全跳过图集,在着色器中从贝塞尔轮廓解析计算覆盖度,任意尺寸与角度下都保持精确。作者同时介绍其 C++20 实现 Slughorn,支持 OpenGL、Vulkan、Web

来源:Hacker News

Magnifying a baked atlas glyph (left and center) versus rendering it from the outline (right).

Valve introduced the fix that carried the industry for a decade. Chris Green’s 2007 SIGGRAPH work, “Improved Alpha-Tested Magnification for Vector Textures and Special Effects,” stores not the glyph’s pixels but a signed distance field: each texel holds the distance to the nearest edge, positive inside, negative outside. In the shader you sample that field and threshold at zero. Because distance interpolates smoothly, you can scale a small SDF texture up dramatically and still get a clean edge, and you get cheap antialiasing by softening the threshold.

One small texture, resolution independent within reason, one cheap shader. For a long time this was the default for crisp UI text and game HUDs, and it still is on constrained hardware.

But an SDF is still a baked texture sampled at a fixed resolution, and it lies about corners. A sharp corner is a discontinuity in the distance field, and bilinear interpolation rounds it off. Every hard corner on a letter, the point of an “A”, the notch of a “K”, gets softened. Push the magnification far enough, or make the glyph small enough that the field is only a few texels wide, and thin stems break up and detail smears.

A signed distance field (left) and the crisp edge a shader recovers from it (right).

Viktor Chlumsky’s work, from his 2015 thesis and the 2018 paper “Improved Corners with Multi-Channel Signed Distance Fields,” fixes the corner problem. Instead of one distance channel, MSDF stores three, in red, green, and blue, each encoding distance to a different subset of edges chosen so that sharp corners survive. In the shader you take the median of the three channels. The median trick reconstructs corners almost perfectly, so an MSDF glyph stays crisp at magnifications that would round an SDF to mush.

MSDF is the current sweet spot for a lot of teams, and Chlumsky’s msdfgen is MIT licensed and widely adopted. If you need crisp scalable text and you are willing to bake an atlas, it is an excellent choice.

It is still an atlas, though, with the costs that implies. You bake each glyph at a chosen resolution ahead of time, so dynamic or user-supplied text, and enormous glyph sets like CJK, still mean baking pipelines and memory budgets. Generation is more expensive than plain SDF. At very small sizes you are still sampling too few texels to hold fine detail, and at extreme minification you still fight aliasing. The three-channel lookup costs more bandwidth than one. MSDF raises the ceiling on quality, but it still uses an atlas.

SDF rounds sharp corners; MSDF preserves them. Images: Viktor Chlumsky / msdfgen (MIT).

A different family skips textures entirely and turns the outline into geometry the GPU can rasterize.

This family is genuinely resolution-independent and, for animated designed vector graphics, often the right answer. Rive in particular is built for artwork that moves. The costs are the tessellation itself, which has to be redone when geometry changes, the geometry blowup for complex glyphs, the difficulty of clean analytic antialiasing, and in some cases a dependence on specific hardware features or extensions.

Tessellation methods turn the outline into triangles the GPU rasterizes.

Slug skips the atlas and per-frame tessellation and keeps the glyph as a list of quadratic Bezier curves and line segments stored in a small GPU buffer. Alongside it, Slug builds a lightweight per-glyph acceleration structure that partitions the glyph into horizontal bands, so a given pixel only has to consider the handful of curves near it rather than the whole outline.

Then it resolves coverage directly in the fragment shader. For each pixel it effectively casts a ray, finds where that ray crosses the nearby Bézier curves, and counts those crossings to compute the winding number and therefore coverage. The hard part, and Lengyel’s secret sauce, is a test he calls root eligibility: a precise rule for which curve-ray intersections should count, so the winding math is exact at the shared endpoints where curves meet and where naive approaches produce cracks or double-counts. Because the shader is solving the curve equations analytically rather than sampling a baked field, it produces exact coverage and clean antialiasing at any scale.

Slug’s core idea: for each pixel, cast a ray and count how many times it crosses the outline.

There is no baked resolution, so the same glyph is razor sharp at 6 pixels or 6000, and it stays sharp under arbitrary 2D and 3D transforms, including perspective, because coverage is computed per pixel after the transform. There is no atlas, so a hundred thousand CJK glyphs cost a font’s worth of outline data, not an atlas the size of a video. Text can change every frame at no baking cost, which is exactly what you want for live data, user input, and localized content. And it all happens in a single draw with an ordinary fragment shader, no vendor extension required.

This is critical when you cannot predict how the text will be viewed. Atlas, SDF, and MSDF all bake a fixed resolution ahead of time, which quietly assumes a bounded range of on-screen sizes and viewing angles. When the relationship between the text plane and the camera is not known in advance, a free 3D camera, an arbitrary zoom, a close-up, or a steep off-axis grazing angle, those baked approximations break down: magnify past the baked resolution and the atlas blurs while SDF and MSDF round and smear, and at oblique angles the sampled field aliases. You cannot pre-allocate enough resolution for every possible view without the storage exploding. Slug computes coverage analytically, per pixel, after the transform, so it stays exact no matter how close, how far, or how oblique the viewer gets, with nothing baked and no ceiling to hit. Tessellation is the only other family that shares this, and it pays for it in tessellation cost and harder antialiasing. That is why Slughorn is the one to reach for in interactive 3D, AR and VR, flythroughs, moving HUDs, and CAD or digital-twin navigation, where you do not get to decide in advance how close or how oblique the viewer will be.

We rendered the same capital R with Slughorn (our osgSlug integration) next to the alternatives you would actually reach for: a single-channel SDF, an MSDF, Rive’s renderer, and osgText’s bitmap along with its bitmap-derived SDF. Every texture-based panel got the same budget, 64 texels per em, so what separates them is technique, not resolution.

Straight on at their baked size, five of the six are practically identical: Slughorn, Rive, and MSDF reproduce the outline, and the single-channel SDFs differ only by slightly rounded corners at the foot of the leg. The outlier is the osgText bitmap, a 64 px/em image magnified about four times, which cannot recover detail it never stored.

Tilt the glyph into perspective and the picture changes.

In grazing perspective, Slughorn and the three distance-field panels keep the R in place with clean edges, because they compute coverage per pixel inside the glyph’s own plane, so the projection costs them nothing. The osgText bitmap blurs as it recedes. Rive’s R is the wrong shape: its renderer only accepts 2D affine transforms, and a perspective projection is not affine, so the best it can do is an approximation that is exact at the center and drifts toward the edges.

At extreme magnification, only the curve-based renderers, Slughorn and Rive, still produce a straight, clean edge, because both work from the actual outline at whatever size it is shown (Rive by re-tessellating every frame for this view). The distance-field panels notch, where interpolating between stored samples no longer matches the true curve. The osgText bitmap has dissolved into a single grey gradient, because each of its texels now covers a large part of the panel.

A note on fairness, because the technical reader will ask. Every texture-based method here used the same 64 texels per em, and giving them more pushes these artifacts back without removing them. The SDF and MSDF panels use default bake settings, and MSDF’s error-correction options would soften some of the notches in the last image. Rive is shown at its best for these views, rendered through the camera every frame, which is more generous than how it is usually embedded in a 3D scene, where it would be drawn into a texture and mapped onto the surface, avoiding the distortion but blurring the way the bitmap does.

Pale green marks where Slug is the best or a tied-best choice.

There is no single winner, there is a right tool per job.

We keep saying text because text is where Slug earned its name, but Slughorn draws anything you can express as filled and stroked vector geometry, from any of its backends. That means full SVG with gradients, layered shaders, and animation within layers, and it holds up at map-cartography scale, drawing labels and linework at clarity and resolution the pre-baked approaches cannot reach. There is a lot more we will be demonstrating soon. For the features we have not called out here, see the Slughorn repository on GitHub.

Slughorn is our implementation of the Slug technique in modern C++20. It does the heavy work once, at build time, so there is no runtime tessellation: the outline data and band structure are prepared ahead of time and the shader just evaluates coverage. And while text is where Slug made its name, Slughorn doesn’t treat glyphs specially: a glyph is just a popular shape. Anything you can describe as vector paths renders through the same pipeline, with the same quality. It ingests the formats you already use, including SVG, FreeType fonts, and paths from Blend2D, Cairo, and Skia, and it exposes a native Canvas-style API for authoring shapes directly, with fills, strokes, and gradients composited into a single GPU-ready atlas of outline data. It targets OpenGL, Vulkan, WebGPU, and DirectX, and ships with Python bindings alongside the C++ API, so the same rendering works from an embedded HUD to a full 3D scene.

Credit where it is due: the Slug algorithm is Eric Lengyel’s, If you are fighting blurry labels in a 3D scene, an atlas that will not fit your glyph set, or text that has to stay crisp while it moves, that is exactly the kind of problem we solve. Contact us to talk about Slughorn or to put it to work in your pipeline.

A texture atlas stores each glyph as a baked bitmap, so it blurs once you scale past its baked size. SDF (signed distance field) stores distance-to-edge instead, which scales better but rounds sharp corners. MSDF adds channels so corners stay crisp, but it is still a baked atlas at a chosen resolution. Slug skips the atlas and computes coverage from the glyph’s actual Bezier outline in the shader, so it stays exact at any size or angle.