<p><strong>EDIT: Please see my other answer with a concrete solution.</strong></p>

<p>I have actually solved this exact problem over a year ago for my master's thesis. In the Valve paper, they show that you can AND two distance fields to achieve this, which works as long as you only have one convex corner. For concave corners, you also need the OR operation. <a href="https://lambdacube3d.wordpress.com/2014/11/12/playing-around-with-font-rendering/" rel="noreferrer">This guy</a> actually developed some obscure system to switch between the two operations using four texture channels.</p>

<p>However, there is a much simpler operation that can facilitate both AND and OR depending on the situation, and this is the principal idea of my thesis: <strong>the median of three</strong>. So basically, you use exactly three channels (ideal for RGB), which are completely interchangeable, and combine them using the median operation (choose the middle value out of the three).</p>

<p>To accomodate anti-aliasing, we don't work with just booleans, but floating point values, and the AND operation becomes the minimum, and the OR becomes the maximum of two values. The median of three can indeed do both: if <em>a</em> &lt; <em>b</em>, for (<em>a</em>, <em>a</em>, <em>b</em>), the median is the minimum, and for (<em>a</em>, <em>b</em>, <em>b</em>), it is the maximum.</p>

<p>The rendering process is still extremely simple. The entire fragment shader including anti-aliasing can look something like this:</p>

<pre><code>int main() {
    // Bilinear sampling of the distance field
    vec3 s = texture2D(sdf, p).rgb;
    // Acquire the signed distance
    float d = median(s.r, s.g, s.b) - 0.5;
    // Weight between inside and outside (anti-aliasing)
    float w = clamp(d/fwidth(d) + 0.5, 0.0, 1.0);
    // Combining the background and foreground color
    gl_FragColor = mix(outsideColor, insideColor, w);
}
</code></pre>

<p>So the only difference from the original method is computing the median right after sampling the texture. You will have to implement the median function though, <a href="https://stackoverflow.com/a/19045659/1189488">which can be done with just 4 min/max operations</a>.</p>

<p>Now of course, the question is, <strong>how do I build such a three-channel distance field?</strong> And this is the tricky part. The most obvious approach that I took in the beginning was to perform a decomposition of the input shape/glyph into three components, and then generate a conventional distance field out of each. The rules for this decomposition aren't that complicated. Firstly , the area with at least 2 out of 3 channels on is the inside. Then, if you imagine this as the RGB color channels, convex corners must be made of a secondary color, and its two primary components continue outward. Concave corners are the inverse: Two secondary colors enclose their common primary color, and the wedge between where both edges continue inward is white. I also found that some padding is necessary where two primary or two secondary colors would otherwise touch to avoid artifacts (for example, in the middle stroke of the "N" in the picture).</p>

<p>The following image is an example decomposition generated by the program from my thesis:</p>

<p><a href="https://i.sstatic.net/2fVah.png" rel="noreferrer"><img src="https://i.sstatic.net/2fVah.png" alt="Multi-channel decomposition of glyphs"></a></p>

<p>This approach however has some drawbacks. One of them is that the special effects, such as outlines and shadows will no longer work correctly. Fortunatelly, I also came up with a second, much more elegant method, which generates the distance fields directly, and even supports all of the graphical effects. It is also included in my thesis and so is also over a year old. I am not going to give any more details right now, because I am currently writing a paper that describes this second technique in detail, but I will post it here as soon as it's finished.</p>

<p>Anyway, here is an example of the difference in quality. The texture resolution is the same in each image, but the left one uses a regular texture, the middle one uses an ordinary distance field, and the right one uses my three-channel distance field. The performance overhead is only the difference between sampling an RGB texture versus a monochrome one.</p>

<p><a href="https://i.sstatic.net/aG1NR.png" rel="noreferrer"><img src="https://i.sstatic.net/aG1NR.png" alt="enter image description here"></a></p>

CC BY-SA 3.0
2016-03-07T13:57:41.343Z
Great first answer, welcome to the Computer Graphics SE! :) Is your thesis publicly available? (Or will it be after you've finished said paper?) If so it would probably be very helpful to link to that, too.
Martin Ender
CC BY-SA 3.0
2016-03-07T14:26:47.413Z
It is supposed to be publicly available, but it seems the school hasn't put it up yet. Anyway, I would prefer not to spread it right now, since the article I'm writing will really explain the important parts much better and focus on how to implement it, and it should be complete very soon.
Detheroc
CC BY-SA 3.0
2016-03-07T15:09:17.600Z
@Detheroc Please notify here and on the gamedev Q when you are done with the article. Explanation's still not 100% clear for me. I would suggest showing the composition step by step in images.
Engineer
CC BY-SA 3.0
2016-03-07T19:36:45.700Z
would love to be able to replicate your current results even if they are not as good as your future results, +1 to sharing whatever details you can.  very exciting.  Have you considered either technique's application towards ray marching (sphere tracing)?  In volume textures or similar...
Alan Wolfe
CC BY-SA 3.0
2016-03-07T23:18:19.107Z
Also, obviously would love to see how it compares to just having a single channel texture that has 3x as many pixels (;
Alan Wolfe
CC BY-SA 3.0
2016-03-11T00:55:40.090Z
@Detheroc This is great. I'm also interested to get notified when you release it publicly.
Felipe Lira
CC BY-SA 3.0
2016-04-26T14:18:21.603Z
@Detheroc Looks really inteestingif you could notify me too when  you release publicly.
Uri Popov
CC BY-SA 3.0
2016-04-26T15:28:18.787Z
@UriPopov Well, I have released the source code publicly, please see my new answer.
Detheroc
CC BY-SA 3.0
2016-04-28T22:00:30.783Z
The thesis is publicly available here: https://dspace.cvut.cz/bitstream/handle/10467/62770/F8-DP-2015-Chlumsky-Viktor-thesis.pdf
Romain Guy
CC BY-SA 3.0
2016-03-07T12:10:32.883Z
2016-04-25T07:59:45.870Z
2017-05-23T12:39:19.850Z
Detheroc
<p>Sorry about the long wait, but it has become obvious that although the article I have promised is basically complete, the publishing process will take some time. Therefore, I have instead prepared an open source program with my new multi-channel distance field construction algorithm, <strong>msdfgen</strong>, which you can try out right now.</p>

<p>It is available on GitHub: <a href="https://github.com/Chlumsky/msdfgen" rel="noreferrer">https://github.com/Chlumsky/msdfgen</a></p>

<p>(I am new to this, so please let me know if there is anything wrong with the repository.)</p>

<p>Someone also asked about how it compares to a larger monochrome distance field, so here is a teaser of the quality difference. However, it really depends on the particular font, and I would not say it is always worth the extra data.</p>

<p><a href="https://i.sstatic.net/dhhiy.png" rel="noreferrer"><img src="https://i.sstatic.net/dhhiy.png" alt="Multi-channel distance field 16x16"></a>
<a href="https://i.sstatic.net/ExmTZ.png" rel="noreferrer"><img src="https://i.sstatic.net/ExmTZ.png" alt="Monochrome distance field 32x32"></a></p>

CC BY-SA 3.0
2016-04-25T07:55:10.327Z
2016-04-25T07:55:10.327Z
Detheroc
<p>I am by no means an expert on the subject, but you might be able to, at least in theory, preserve sharp corners in a monochromatic pseudo-SDF if you used either a Bilateral filter a Directional Bicubic filter instead of a standard Bilinear filter. Besides the obvious benefit of saving memory, you could also have multiple channels for multi-colored SDF decals.</p>

<p>Alternatively, if you don't mind using a second channel, then you could also try having one channel for Horizontal Distance and another for Vertical Distance, and use a Difference of Laplacean (DoL) energy pyramid to compress the texture so that redundant information wouldn't be recorded.</p>

<p>A third and final theoretical solution would be to experiment with a Hexagonally Sampled Texture via Array Set Addressing.</p>

<p>Unfortunately, I don't have any means of testing my ideas currently, and have been unable to find any documents describing or testing anything similar to my ideas. I will link all of the relevant articles where I got my information/ideas shortly.</p>

CC BY-SA 3.0
2017-08-06T16:26:37.153Z
2017-08-06T16:40:20.090Z
2017-08-06T16:40:20.090Z
Amaroq Starwind
<p>Adam Simmons has done some interesting work in this area. I don't know specifically how he's achieving it, but his SDF-based vector rendering is the sharpest I've seen in practice outside of Valve. <a href="http://twitter.com/adamjsimmons/status/611677036545863680">http://twitter.com/adamjsimmons/status/611677036545863680</a></p>

CC BY-SA 3.0
2016-03-07T12:21:17.253Z
Of course I don't have all the details, but it seems to me that this person merely used a pseudo-distance field in place of a regular one, which has already been demonstrated in a 2006 paper by Qin, McCool and Kaplan, "Real-time texture-mapped vector glyphs", which is also referenced in the Valve paper. It only affects the miters of outlines and does nothing to improve the appearance of corners. I suspect the reason it looks sharp is because he uses unpractically large distance field textures. I might be wrong though.
Detheroc
CC BY-SA 3.0
2015-08-21T21:26:46.043Z
2015-08-21T21:26:46.043Z
warrenm
<p>Signed Distance Fields (SDFs) was presented as a fast solution to achieve resolution independent font rendering by Valve <a href="http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf">in this paper</a>. </p>

<p>I already have the Valve solution working but I'd like to preserve sharpness around corners. Valve states that their method can achieve sharp corners by using a second texture channels ANDed with the base one, but lacks to explain how this second channel would be generated. </p>

<p>In fact there's a lot of implementation details left out of this paper. </p>

<p>I'd like to know if any of you could point me out a direction to get SDFs font rendering with sharp corners.</p>

CC BY-SA 3.0
2015-08-21T13:02:37.213Z
As a matter of fact Adam already posted source code on shadertoy. Here's the link: https://www.shadertoy.com/view/ltXSDB
Felipe Lira
CC BY-SA 3.0
2015-08-21T14:33:14.070Z
You had me excited.  He did post the bezier stuff on shadertoy but not the texture distance field stuff!
Alan Wolfe
CC BY-SA 3.0
2015-08-21T22:07:14.407Z
@AlanWolfe I think he has done only for procedurally set bezier curves. I'm not sure the effort required to integrate this into a ttf render lib. When I have some time I'll take a look at it.
Felipe Lira
CC BY-SA 3.0
2015-08-21T22:11:30.443Z
it looks he has some magic sauce on the side of actually storing and retrieving the distances from a texture.  Without a texture in play, the shadertoy examples are missing that part of the equation.
Alan Wolfe
CC BY-SA 3.0
2015-08-31T21:56:04.753Z
Bit late to the party but this older thread from reddit has a ton of info on various methods of improving the sharpness of SDF based rendering: https://www.reddit.com/r/gamedev/comments/2879jd/just_found_out_about_signed_distance_field_text/
Necrolis
<texture><signed-distance-field><font-rendering>
Sharp Corners with Signed Distance Fields Fonts
Felipe Lira
# Sharp Corners with Signed Distance Fields Fonts

(Asked by: username_0 on 2015-08-20)

Signed Distance Fields (SDFs) was presented as a fast solution to achieve resolution independent font rendering by Valve [in this paper](http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf).

I already have the Valve solution working but I'd like to preserve sharpness around corners. Valve states that their method can achieve sharp corners by using a second texture channels ANDed with the base one, but lacks to explain how this second channel would be generated.

In fact there's a lot of implementation details left out of this paper.

I'd like to know if any of you could point me out a direction to get SDFs font rendering with sharp corners.

**Tags:** texture, signed-distance-field, font-rendering

> **Comments:**
> **username_0**: As a matter of fact Adam already posted source code on shadertoy. Here's the link: https://www.shadertoy.com/view/ltXSDB  
> **username_1**: You had me excited.  He did post the bezier stuff on shadertoy but not the texture distance field stuff!  
> **username_0**: @username_1 I think he has done only for procedurally set bezier curves. I'm not sure the effort required to integrate this into a ttf render lib. When I have some time I'll take a look at it.  
> **username_1**: it looks he has some magic sauce on the side of actually storing and retrieving the distances from a texture.  Without a texture in play, the shadertoy examples are missing that part of the equation.  
> **username_2**: Bit late to the party but this older thread from reddit has a ton of info on various methods of improving the sharpness of SDF based rendering: https://www.reddit.com/r/gamedev/comments/2879jd/just_found_out_about_signed_distance_field_text/  

---

## 4 answers

### Answer by username_9 on 2015-08-21. Score: 7 | Accepted answer

Adam Simmons has done some interesting work in this area. I don't know specifically how he's achieving it, but his SDF-based vector rendering is the sharpest I've seen in practice outside of Valve. <http://twitter.com/adamjsimmons/status/611677036545863680>

> **Comments:**
> **username_3**: Of course I don't have all the details, but it seems to me that this person merely used a pseudo-distance field in place of a regular one, which has already been demonstrated in a 2006 paper by Qin, McCool and Kaplan, "Real-time texture-mapped vector glyphs", which is also referenced in the Valve paper. It only affects the miters of outlines and does nothing to improve the appearance of corners. I suspect the reason it looks sharp is because he uses unpractically large distance field textures. I might be wrong though.  

---

### Answer by username_3 on 2016-03-07. Score: 79

**EDIT: Please see my other answer with a concrete solution.**

I have actually solved this exact problem over a year ago for my master's thesis. In the Valve paper, they show that you can AND two distance fields to achieve this, which works as long as you only have one convex corner. For concave corners, you also need the OR operation. [This guy](https://lambdacube3d.wordpress.com/2014/11/12/playing-around-with-font-rendering/) actually developed some obscure system to switch between the two operations using four texture channels.

However, there is a much simpler operation that can facilitate both AND and OR depending on the situation, and this is the principal idea of my thesis: **the median of three**. So basically, you use exactly three channels (ideal for RGB), which are completely interchangeable, and combine them using the median operation (choose the middle value out of the three).

To accomodate anti-aliasing, we don't work with just booleans, but floating point values, and the AND operation becomes the minimum, and the OR becomes the maximum of two values. The median of three can indeed do both: if *a* < *b*, for (*a*, *a*, *b*), the median is the minimum, and for (*a*, *b*, *b*), it is the maximum.

The rendering process is still extremely simple. The entire fragment shader including anti-aliasing can look something like this:

```
int main() {
    // Bilinear sampling of the distance field
    vec3 s = texture2D(sdf, p).rgb;
    // Acquire the signed distance
    float d = median(s.r, s.g, s.b) - 0.5;
    // Weight between inside and outside (anti-aliasing)
    float w = clamp(d/fwidth(d) + 0.5, 0.0, 1.0);
    // Combining the background and foreground color
    gl_FragColor = mix(outsideColor, insideColor, w);
}

```

So the only difference from the original method is computing the median right after sampling the texture. You will have to implement the median function though, [which can be done with just 4 min/max operations](https://stackoverflow.com/a/19045659/1189488).

Now of course, the question is, **how do I build such a three-channel distance field?** And this is the tricky part. The most obvious approach that I took in the beginning was to perform a decomposition of the input shape/glyph into three components, and then generate a conventional distance field out of each. The rules for this decomposition aren't that complicated. Firstly , the area with at least 2 out of 3 channels on is the inside. Then, if you imagine this as the RGB color channels, convex corners must be made of a secondary color, and its two primary components continue outward. Concave corners are the inverse: Two secondary colors enclose their common primary color, and the wedge between where both edges continue inward is white. I also found that some padding is necessary where two primary or two secondary colors would otherwise touch to avoid artifacts (for example, in the middle stroke of the "N" in the picture).

The following image is an example decomposition generated by the program from my thesis:

[![Multi-channel decomposition of glyphs](https://i.sstatic.net/2fVah.png)](https://i.sstatic.net/2fVah.png)

This approach however has some drawbacks. One of them is that the special effects, such as outlines and shadows will no longer work correctly. Fortunatelly, I also came up with a second, much more elegant method, which generates the distance fields directly, and even supports all of the graphical effects. It is also included in my thesis and so is also over a year old. I am not going to give any more details right now, because I am currently writing a paper that describes this second technique in detail, but I will post it here as soon as it's finished.

Anyway, here is an example of the difference in quality. The texture resolution is the same in each image, but the left one uses a regular texture, the middle one uses an ordinary distance field, and the right one uses my three-channel distance field. The performance overhead is only the difference between sampling an RGB texture versus a monochrome one.

[![enter image description here](https://i.sstatic.net/aG1NR.png)](https://i.sstatic.net/aG1NR.png)

> **Comments:**
> **username_4**: Great first answer, welcome to the Computer Graphics SE! :) Is your thesis publicly available? (Or will it be after you've finished said paper?) If so it would probably be very helpful to link to that, too.  
> **username_3**: It is supposed to be publicly available, but it seems the school hasn't put it up yet. Anyway, I would prefer not to spread it right now, since the article I'm writing will really explain the important parts much better and focus on how to implement it, and it should be complete very soon.  
> **username_5**: @username_3 Please notify here and on the gamedev Q when you are done with the article. Explanation's still not 100% clear for me. I would suggest showing the composition step by step in images.  
> **username_1**: would love to be able to replicate your current results even if they are not as good as your future results, +1 to sharing whatever details you can.  very exciting.  Have you considered either technique's application towards ray marching (sphere tracing)?  In volume textures or similar...  
> **username_1**: Also, obviously would love to see how it compares to just having a single channel texture that has 3x as many pixels (;  
> **username_0**: @username_3 This is great. I'm also interested to get notified when you release it publicly.  
> **username_6**: @username_3 Looks really inteestingif you could notify me too when  you release publicly.  
> **username_3**: @username_6 Well, I have released the source code publicly, please see my new answer.  
> **username_7**: The thesis is publicly available here: https://dspace.cvut.cz/bitstream/handle/10467/62770/F8-DP-2015-Chlumsky-Viktor-thesis.pdf  

---

### Answer by username_3 on 2016-04-25. Score: 50

Sorry about the long wait, but it has become obvious that although the article I have promised is basically complete, the publishing process will take some time. Therefore, I have instead prepared an open source program with my new multi-channel distance field construction algorithm, **msdfgen**, which you can try out right now.

It is available on GitHub: <https://github.com/Chlumsky/msdfgen>

(I am new to this, so please let me know if there is anything wrong with the repository.)

Someone also asked about how it compares to a larger monochrome distance field, so here is a teaser of the quality difference. However, it really depends on the particular font, and I would not say it is always worth the extra data.

[![Multi-channel distance field 16x16](https://i.sstatic.net/dhhiy.png)](https://i.sstatic.net/dhhiy.png)
[![Monochrome distance field 32x32](https://i.sstatic.net/ExmTZ.png)](https://i.sstatic.net/ExmTZ.png)


---

### Answer by username_8 on 2017-08-06. Score: 1

I am by no means an expert on the subject, but you might be able to, at least in theory, preserve sharp corners in a monochromatic pseudo-SDF if you used either a Bilateral filter a Directional Bicubic filter instead of a standard Bilinear filter. Besides the obvious benefit of saving memory, you could also have multiple channels for multi-colored SDF decals.

Alternatively, if you don't mind using a second channel, then you could also try having one channel for Horizontal Distance and another for Vertical Distance, and use a Difference of Laplacean (DoL) energy pyramid to compress the texture so that redundant information wouldn't be recorded.

A third and final theoretical solution would be to experiment with a Hexagonally Sampled Texture via Array Set Addressing.

Unfortunately, I don't have any means of testing my ideas currently, and have been unable to find any documents describing or testing anything similar to my ideas. I will link all of the relevant articles where I got my information/ideas shortly.


---

