Chlumsky/msdfgen

fwidth(sigDist) seems to be the wrong AA measure for MSDF

makepaddev opened this issue · 13 comments

Hi! thanks for your awesome MSDF generator, im so happy you solved that :)

One note on your GLSL though, i got really ugly results using fwidth(sigDist) as AA especially at small font sizes.
I'm currently using a scale of this value:

  1. / length(vec2(dFdx(p.x), dFdy(p.y)))
    where p is the texel coordinates of the input texture.
    Note that you just scale it till it looks right (it depends on how big the glyph size is in texels)
    You can see it live and kicking in webGL here:
    makepad.github.io/makepad.html
    You can use cmd+/- like a webpage to zoom in/out (use chrome)
    Hope this helps

Thanks again

Yes, it is absolutely the wrong measure. I don't understand why everyone just blindly copies it even though it is labeled as an example. The reason I used it as a simple demo was because it's the only way that doesn't require you to remember the SDF range.

Your version isn't perfect either though, it won't work if the text is rotated (try 90°).

Right, since i do x in x and y in y. Hmm good point, i'll see if i can find something better. How about
length(vec2(length(dFdx(p)), length(dFdy(p)))). That looks fine here. Also AA stuff is hard enough that people just take the example. It takes a fair amount of fiddling to come up with something better. I took the idea (or maybe now even the exact same thing after you pointed out 90 deg) from glyphy

Btw i also wrote a font-file generator that groups each color channel separately instead of interleaving, it gets you up to 25% better zip performance of the total msdf font, could be a nice-to-know idea.

If you're not drawing the text in a 3D perspective, there is no point in computing these derivatives, and you should just use a constant (proportional to pixel size and SDF range).

Ok thanks, although the idea is that this font shader also works in VR without change. But for 2D i could compute a constant yes.

Just saw #22 this is a duplicate,
Still could be worth it to point it out in the example with big caps :)

@Chlumsky Would you be willing to share an example of a shader that uses a constant instead of computing derivatives, per your comment?

If you're not drawing the text in a 3D perspective, there is no point in computing these derivatives, and you should just use a constant (proportional to pixel size and SDF range).

How do I calculate that constant value?

To answer your other question, I think that people blindly copy your example because, like me, they really want your amazing font display in their games, but don't have the expertise to create shader code from a mathematical description.

Thank you for publishing this work!

Alright, here is the modified example fragment shader:

in vec2 pos;
out vec4 color;
uniform sampler2D msdf;
uniform float distanceFactor;
uniform vec4 bgColor;
uniform vec4 fgColor;

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

void main() {
    vec3 sample = texture(msdf, pos).rgb;
    float sigDist = distanceFactor*(median(sample.r, sample.g, sample.b) - 0.5);
    float opacity = clamp(sigDist + 0.5, 0.0, 1.0);
    color = mix(bgColor, fgColor, opacity);
}

Now, set distanceFactor to the scale of the MSDF texture in the output image, times pxRange (the value you set when generating the distance field, 2 is the default if you didn't set it).

For example, if you're drawing the letter A into a rectangle of 48x64 pixels and it is encoded as a segment of 15x20 texels in the SDF texture, the scale is 3.2. If you used a pxRange of 3, you would set distanceFactor to 9.6.

By the way, I eventually changed the example so that it uses the correct derivative, but now it requires the pxRange value, as well as the size of the input texture. Using the example is fine for a quick prototype, but I assumed that anyone who would want to use it in a software that is going to be released, would put in the minimal effort to find out what they're doing. I also assumed that since the original SDF text method had been around for many years, there would be plenty of resources online that explain how to use distance fields (my version only adds the median step, otherwise it's the same) but I admit that it's actually not that easy to find for some reason.

Now I realize that I should have probably written up a layman's guide on what to do and what not to do when generating and using the distance fields, but I guess it's too late now.

This is fantastic! Thank you so much for taking the time to do this!

And yes, I've spent a couple of days now searching everything I could find about SDF and trying many variations of the shader without the derivatives, all with terrible results. I've also been unable to find anyone not using derivatives specifically for MSDF, so this is going to be an amazing resource.

I actually have your example with the correct derivative and pxRange value working on Windows and was happy with the result, but then when I attempted to port to mobile devices I discovered that my game engine (MonoGame, which depends on MojoShader) does not support the necessary GLSL level required for dFdx/dFdy (or fwidth). Then I discovered your comment above about using a constant value, which seemed to be the perfect solution since I imagine it would be much more mobile device friendly.

Anyway, thank you again and I'm excited to try to get this working on mobile devices again today, armed with this information.

The challenge will be getting the pixel size of the output, since that's buried in a scene graph, viewport, device-independent pixels, etc. I'm sure I'll be able to figure it out, but I'm not sure how expensive it will be in terms of performance.

Oh, and I don't think it's too late for that layman's guide. :) I've done a ton of searching, and there is a real dearth of information about the shader portion of MSDF out there, and the few shader implementations that I did find seem questionable in terms of how they're doing the anti-aliasing.

This was a godsend for me, thank you. I implemented the shader with this method and it works very well. This post helped me to understand the mechanics of msdf. I actually wanted to read the paper but every link I clicked on was 404. Is there a link to the canonical paper reference somewhere?

Do you mean this paper? Also the original thesis is linked in the Readme.