freetype-gl icon indicating copy to clipboard operation
freetype-gl copied to clipboard

distance field fragment shader problem

Open hrouault opened this issue 9 years ago • 15 comments

I am using the fragment shader available in the demos distance-field-2.frag. When scaling down glyphs, I observed some pixels on the boundaries are displayed as the inside of the glyphs when they should be displayed almost like the outside. I think this is due to the derivative of the distance approaching 0, which makes the smooth step function being 0 even for small values of the distance. A quick and dirty hack was to change

    float width = fwidth(dist);

into

    float width = max(fwidth(dist), 0.2);

There must be something better to do.

hrouault avatar Feb 06 '16 21:02 hrouault

Thanks for the report. Could you post some screenshots because I'm not sure I visualize the problem you mention.

rougier avatar Feb 06 '16 21:02 rougier

This is before the modification: before and after: after Some examples that I don't have at hand anymore were even more striking.

hrouault avatar Feb 06 '16 21:02 hrouault

Thanks. Could you make a PR ?

rougier avatar Feb 07 '16 06:02 rougier

I'll try to find a better hack in the next few days. Then I'll submit a PR.

hrouault avatar Feb 08 '16 15:02 hrouault

For minification, if you want to consider a "full-blown" hack, I've made one in python:

The idea is to use different interpolation depending on the glyph screen size. I used a bicubic filter but I guess it is ok with bilinear if you don't want to code it.

I think I took the idea from this thread (but I'm not even sure):

http://www.java-gaming.org/topics/signed-distance-field-fonts-look-crappy-at-small-pt-sizes/33612/msg/315893/view/topicseen.html#msg315893

rougier avatar Feb 08 '16 17:02 rougier

This code:

    float dist = texture2D(u_texture, gl_TexCoord[0].st).r; 
    float width = fwidth(dist); 
    float alpha = smoothstep(0.5-width, 0.5+width, dist); 

looks wrong to me. width should be the size of a pixel. That is, something like fwidth(gl_TexCoord[0].st), adjusted for dimensions. I think fwidth() might not do that. In GLyphy I use this instead:

  /* isotropic antialiasing */ 
  vec2 dpdx = dFdx (p); 
  vec2 dpdy = dFdy (p); 
  float m = length (vec2 (length (dpdx), length (dpdy))) * SQRT2_2; 

where p is the texture coordinate essentially.

I'm not sure how it works currently.

behdad avatar Feb 09 '16 10:02 behdad

Ok I see how fwidth(dist) is supposed to work. Still, I think my solution is more correct, exactly when dist is small.

behdad avatar Feb 09 '16 10:02 behdad

Thanks @behdad ! @hrouault, does this fix the problem ?

rougier avatar Feb 09 '16 14:02 rougier

It introduces new problems. Here is the fragment shader I use for the texture gradient method:

#version 330 core
uniform sampler2D u_texture;

in vec2 UV;
in vec4 FrontColor;

out vec4 fragcolor;

float contour(in float d, in float w) {
    return smoothstep(0.5 - w, 0.5 + w, d);
}

float samp(in vec2 uv, float w) {
    return contour(texture(u_texture, uv).r, w);
}

void main(void)
{
    float dist = texture(u_texture, UV).r;
    vec2 dpdx = dFdx (UV); 
    vec2 dpdy = dFdy (UV); 
    float w = length (vec2 (256 * length (dpdx), 256 * length (dpdy))) * 0.707; 
    float alpha = contour( dist, w );
    fragcolor = vec4(FrontColor.rgb, alpha);
}

This is the result I obtain for the texture gradient: texture_gradient and for the supersampling: supersampling

I think the use of fwidth bring some robustness: noise in the distance is compensated. I'll add a pull request for the supersampling, without bicubic interpolation for magnification.

hrouault avatar Feb 09 '16 16:02 hrouault

Looking at the distance-field-2.c example, I still see some imperfections: remaining-defects (look at the "p" and "v" in particular). They seem to be easy to correct since the spurious traces are far from the glyph. Do you know what causes these and how to correct them?

hrouault avatar Feb 09 '16 21:02 hrouault

This is the result I obtain for the texture gradient: texture_gradient and for the supersampling: supersampling

How is the e rendered? So, e looks fine, d doesn't? Then I suggest you debug that.

I think the use of fwidth bring some robustness: noise in the distance is compensated.

Meh.

I'll add a pull request for the supersampling, without bicubic interpolation for magnification.

I suggest you get to the root of the problem, instead of averaging multiple number to get "better" output.

behdad avatar Feb 10 '16 03:02 behdad

How is the e rendered? So, e looks fine, d doesn't? Then I suggest you debug that.

Well, this is not trivial... The supersampling does debug that.

I suggest you get to the root of the problem, instead of averaging multiple number to get "better" output.

This is the root of the problem: correct the interpolation when minifying. I was just using the library in a project and noticed these problems. I am now satisfy with the supersampling solution. If you don't, please feel free to reject the PR.

hrouault avatar Feb 10 '16 03:02 hrouault

If you don't, please feel free to reject the PR.

I'm not the developer for freetype-gl; Nicolas is. I'm interested in figuring out what the correct way is. Thanks for your contribution :).

behdad avatar Feb 10 '16 03:02 behdad

I suspect the trace may comes from surrounding glyphs in the texture. There is a parameter in the texture atlas to add some space between glyphs, it might be worth a try to check if this fix the problem.

Also, it seems the top of the O and the T are cut in your example.

rougier avatar Feb 10 '16 06:02 rougier

Adding some space around the glyphs does fix the spurious traces problem but doesn't fix the first problem (but I need to check more). Thanks! For reference, here is were to change the glyph spacing: https://github.com/rougier/freetype-gl/blob/0c6da62d03832f42c52c25d8cee56b68554971e0/texture-font.c#L610-L613

hrouault avatar Feb 11 '16 15:02 hrouault