惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
I
InfoQ
B
Blog RSS Feed
D
Docker
GbyAI
GbyAI
N
Netflix TechBlog - Medium
Y
Y Combinator Blog
F
Fortinet All Blogs
P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
人人都是产品经理
人人都是产品经理
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
M
MIT News - Artificial intelligence
C
Check Point Blog
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
博客园 - Franky
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Last Week in AI
Last Week in AI
L
LangChain Blog

Red Blob Games: latest blog posts

Red Blob Games: Differential heuristics Red Blob Games: Responsive design calculator Red Blob Games: Academic citations Red Blob Games: Highlighting interactive code blocks Red Blob Games: Optimizing page size Red Blob Games: Writing a guide to SDF fonts Red Blob Games: URLs with trailing punctuation Red Blob Games: What I did in 2025 Red Blob Games: Goodbye Sass Goodbye Sass Red Blob Games: RotMG map seeds Red Blob Games: Mapgen4 Mapgen4's use of WebGL2 Red Blob Games: Mapgen4 river shader Red Blob Games: Mapgen4 renderer Red Blob Games: Harnessing ChatGPT hallucinations Red Blob Games: Let Let's write a search engine, part 2 of 2 Red Blob Games: Let Let's write a search engine, part 1 of 2 Red Blob Games: Hexagon conversions Red Blob Games: Mapgen4 trade routes Red Blob Games: De-optimizing mapgen4 Red Blob Games: Hexagon spiral coordinates Red Blob Games: Thoughts on Flash Red Blob Games: What I did in 2024 Red Blob Games: Hexagon page animations Red Blob Games: SDF Halos SDF Halos Red Blob Games: SDF headless tests, part 1
Red Blob Games: SDF headless tests, part 2
2024-11-18 · via Red Blob Games: latest blog posts
Blog post: 17 Nov 2024

In the last blog post I wrote about how I wanted to test the parameters of msdfgen[1], which generates multi-signed distance fields for fonts and other shapes. While testing the emrange parameter, I found lots of bugs in my renderer.

I also wanted to try out msdfgen’s new asymmetric range parameter, aemrange. The distance field goes both inside and outside the font edge, but if I want outlines, I want it to go pretty far outside the font. I made a visualization to show the range:

Contour lines showing the distance field

From this we can see that the interior has far fewer contour lines than the exterior. That’s because fonts are relatively thin. If it were a large object like a solid circle, it would have many contour lines on the interior.

The encoding of the distance field maps distances to a value 0.0–1.0, which gets stored in the texture as 0–255. By default, the encoding sets distance=0 to value=0.5 which will be 127.5. Let’s look at how much of the font space is used by each encoded value. Blue means I’m using that value for the interior of the font. Green means I’m using it for the exterior of the font. Gray means I’m not using that value.

Symmetric use of 0-255 values within the font atlas image

This histogram tells us two things:

  1. Of all the pixels in the distance field encoding, most of them are used (blue or green). There’s not a lot of waste representing values that I don’t need (gray). This is good.
  2. Of all the 0–255 values in the encoding, around half of them are used. There’s a lot of waste on the right side where no pixels use that value. This is bad.

Most of the values I care about are on the left half. MSDFgen v1.12 and MSDF-Atlas-Gen 1.3 add support for an asymmetric encoding. I switched from -emrange 0.8 (which corresponds to -0.4 to +0.4) to -aemrange -0.7 0.1, keeping the full range at 0.8.

Does this improve the quality?

Symmetric vs asymmetric use of range

It did not improve quality! If anything it’s slightly worse. What’s going on?

Keeping the full range at 0.8 was the problem. I had shifted the range, which extended the contour lines out farther, but didn’t make them more accurate:

Asymmetric shifted use of 0-255 values
  1. Of all the pixels in the distance field encoding, fewer of them than before are being used (blue or green), and a lot more are wasted (gray). This is bad.
  2. Of all the 0–255 values in the encoding, around half of them are used. There’s less wasted on the right side but more wasted on the left side. This is bad.

I instead want to stretch the range, with -aemrange -0.4 0.1:

Asymmetric stretched use of 0-255 values
Symmetric vs asymmetric use of range
  1. Of all the pixels in the distance field encoding, most of them are being used (blue or green). This is good.
  2. Of all the 0–255 values in the encoding, msot of them are being used. This is good.

The output does look better. The contour lines don’t go out farther; instead they are spaced more closely together, which means more precision. The curves are smoother.

Asymmetry helps by throwing away the range that is unused. I also wanted to check for the opposite problem: that some value is needed but doesn’t fit in the 0–255 range. I added code to the shader to detect this:

if (distances.g >= 1.0) gl_FragColor = vec4(1, 0.5, 0, 1);

and here’s what it looks like if I cut off too much of the range:

If the range is too asymmetric, the interior distances get clipped

This is an example of a shader “assert” that helps me figure out when something is wrong.

What is the optimal aemrange? It took me way too long to realize that I should set the low and high endpoints independently:

  1. Low endpoint is based on the maximum range I want for outline, halo, and other special effects.
  2. High endpoint is based on the maximum distance value inside the font, as calculated by msdfgen.

Setting the range this way should maximize font rendering quality. In both cases the range needs to be extended slightly beyond the limit so that the texture interpolation can find an accurate value. But I wasn’t so smart. I tweaked repeatedly until I found values that were good enough. Writing this blog post helped me understand what is going on, and I’ll be able to choose better next time.