Chlumsky / msdfgen

Multi-channel signed distance field generator
MIT License
3.97k stars 412 forks source link

Understanding AAing example, problems at small sizes? #115

Closed deklanw closed 3 years ago

deklanw commented 3 years ago

I'm using the AAing example given in the README and it seems to be failing at smaller sizes. I'm trying to understand how it works well enough to fix the issue. Finding information on this is proving difficult (top google results lead to the issues in this repo).

    vec4 renderMsdf(vec2 p) {
      vec2 msdfUnit = u_pxRange/vec2(textureSize(u_texture, 0));
      vec3 sampled = texture(u_texture, p).rgb;

      float sigDist = median(sampled.r, sampled.g, sampled.b) - 0.5;
      sigDist *= dot(msdfUnit, 0.5/fwidth(p));

      float opacity = clamp(sigDist + 0.5, 0.0, 1.0);

      return vec4(u_textColor, opacity);
    }

In particular, I'm lost at this line

sigDist *= dot(msdfUnit, 0.5/fwidth(p));

Could you explain what is going on in this line? Why dot product? 0.5? msdfUnit is a measure of range-pixels/texel and fwidth(p) is a measure of change-in-texel/screen-pixel. After this multiplication what does sigDist measure?

Here is the issue:

Results look fine at large sizes:

image

But poor at smaller sizes:

image

image

Generated the atlas using msdf-bmfont-xml with pxRange of 27 (not sure if it's possible for this to be too high, but it's the max I could stuff into the image), all ASCII characters in 512x512. Rendered with WebGL2 on Chrome.

I have another shader with stroke and thickness based (faithfully, I hope) on your example here https://gist.github.com/Chlumsky/263c960ae0a7df59afc2da4051eb0553

    vec4 renderMsdfWithEffects(vec2 p) {
        float thickness = 0.1;
        float border = 0.1;

        vec2 msdfSize = vec2(textureSize(u_texture, 0));
        float pxSize = min(0.5/u_pxRange * dot(msdfSize, fwidth(p)), 0.25);

        vec3 msd = texture(u_texture, p).rgb;
        float sd = 2.0 * median(msd.r, msd.g, msd.b) - 1.0 + thickness;
        float inside = linearStep(-border-pxSize, -border+pxSize, sd);
        float outsideBorder = border > 0.0 ? linearStep(+border-pxSize, +border+pxSize, sd) : 1.0;

        return vec4(mix(u_borderColor, u_textColor, outsideBorder), inside);
    }

The same problem at small sizes is occurring here. And, likewise I'm wondering about this similar-looking line

float pxSize = min(0.5/u_pxRange * dot(msdfSize, fwidth(p)), 0.25);

What does pxSize measure? Why the min with 0.25?

In my googling I found this WebGL game framework which also seems to be running into an issue https://github.com/playcanvas/engine/issues/830 at small sizes (their proposed solution is to use a bitmap font at small sizes).

Any ideas on what could be happening here? Could this be some WebGL-specific problem?

Chlumsky commented 3 years ago

You need to post the texture you're using.

deklanw commented 3 years ago

Sure,

msdf

Chlumsky commented 3 years ago

Sorry, but I cannot reproduce the issue, here is my output using the shader you referenced:

image

deklanw commented 3 years ago

Hmm. Rendering via OpenGL? I'm wondering if it's WebGL-specific or somehow related to how I'm loading the texture, etc.

I can put together a Codesandbox reproduction.

Chlumsky commented 3 years ago

There won't be any difference in WebGL. Seems to me like your texture coordinates are wrong.

deklanw commented 3 years ago

Definitely sounds possible, lots of places to make a mistake. But, so I know where to look, in what way could they be wrong such that text is fine at large sizes?

Chlumsky commented 3 years ago

My best guess is that it could be still wrong at large sizes but by the same amount of pixels, so it ends up negligible, but I have no idea how you make the quads. You should also check that you're using the right texture filtering and maybe try replacing derivatives with a constant / uniform. In any case, it doesn't seem like this is an issue with my software.

deklanw commented 3 years ago

Indeed, it seems you were right with the texture filtering part.

The default for gl.TEXTURE_MIN_FILTER is gl.NEAREST_MIPMAP_LINEAR. Changing it to gl.NEAREST or gl.LINEAR looks much better.

Thanks for getting back to me so quickly on this issue. Indeed, nothing to do with your software. Sorry for wasting time. Hopefully this helps someone else out there