Experience-Monks / three-bmfont-text

renders BMFont files in ThreeJS with word-wrapping
http://jam3.github.io/three-bmfont-text/test/
MIT License
789 stars 171 forks source link

better smoothing upon scale #1

Closed Kuranes closed 8 years ago

Kuranes commented 9 years ago

Started here https://twitter.com/tuan_kuranes/status/576412830964031488 , for aa text more independent of transformations:

derivatives gives you gradient, to always get correct smoothstep see @cthulhim slides http://www.essentialmath.com/GDC2015/VanVerth_Jim_DrawingAntialiasedEllipse.pdf or in skia code https://github.com/google/skia/blob/master/src/gpu/effects/GrDistanceFieldTextureEffect.cpp#L87

then

If you're only using uniform scale, you could do smoothstep w/afwidth = sqrt(2)/(2*scale). Needs a uniform, though. It may be better to compute 1/scale from the view matrix in the vertex shader and pass afwi The aim is to correct distance upon transformation, if it's a uniform scale, just getting scale should be enough

Adding:

The better the scale info, the more precise the smoothing and more readable the text upon distance to camera Scale from view camera scale, depth position, linearized gl_FragCoord.z, anything that give a precise transform scale applied to the text should help there ?

Also in the very particular case of that demo, effect intended can be checked/simulated using gl_FragCoord (just tested quickly that in firefox shader debugger)

float smooth2 = smooth*sqrt(2.)/(2.*gl_FragCoord.w);
float alpha = smoothstep(0.5 - smooth2, 0.5 + smooth2, dst);

somehow works because perspective projection here (smooth might need some change too to adapt the sdf texture input/scale)

But imho, for a library you might want to implement the most robust method possible, which means handling all cases, including non-uniform scale (think text on clothes (real time physically deformed or on scale animated character, etc.) and for that you might want use the whole derivatives thing from the slides.

mattdesl commented 9 years ago

I just tested the gl_FragCoord.w trick you mentioned, looks way better. :+1:

I will look into derivatives over the coming days to see what can be done there.

Kuranes commented 9 years ago

great :) Make sure to read/discuss on http://www.essentialmath.com/blog/?p=111 That should should help and clarifies

behdad commented 9 years ago

You can use the derivatives, like this:

https://github.com/behdad/glyphy/blob/master/demo/demo-fshader.glsl#L49

In WebGL, you have to enable them using an extension.

mattdesl commented 9 years ago

Thanks guys. I've been looking into this a bit more, and found a good article on the subject:

2D Shape Rendering (code)

His aastep() looks basically the same as Glyphy's. Now I'm comparing the following two options:

//A
float afwidth = smooth * length(vec2(dFdx(dst), dFdy(dst))) * SQRT2_2;
//B
float afwidth = smooth * SQRT2 / (2.0*gl_FragCoord.w);

The rest of the shader:

#define THRESHOLD 0.5
#define SQRT2 1.4142135623730951
#define SQRT2_2 0.70710678118654757

    ...
    float alpha = smoothstep(THRESHOLD-afwidth, THRESHOLD+afwidth, D);
    gl_FragColor = vec4(color, opacity * alpha);

demo

My crude and unscientific findings:

I've also tried Gustavson's explicit texel interpolation, but have a hard time seeing any differences.

For this particular library I may expose some or all of these options with #ifdefs, and document some of the issues with mipmapping.

jvanverth commented 9 years ago

The problem with the vec2(dFdx(dst), dFdy(dst)) approximation is that Gustafson is assuming that the gradient of a distance field is always unit length, so vec2(dFdx(dst), dFdy(dst)) is that unit length vector multiplied by the local inverse transform. Mathematically, that's correct, but I've found that dFdx() and dFdy() aren't always that great at computing the gradient at the edges of glyphs, so with an identity transform vec2(dFdx(dst), dFdy(dst)) isn't always unit length. This might be why you see some artifacts with this approach. Skia explicitly normalizes the gradient and multiplies it by the Jacobian because of this. That may be overkill for what you're trying to do, though.

behdad commented 9 years ago

Note that in GLyphy I don't do vec2(dFdx(dst), dFdy(dst)), but vec2(dFdx(texCoord), dFdy(texCoord)) and from that divide by texture size, to get to the pixel size. It's similar, but different.

mattdesl commented 8 years ago

This module now uses standard derivatives in the SDF shader, and only uses gl_FragCoord.w as a fallback (rarely needed).

For reference: https://github.com/Jam3/three-bmfont-text/blob/8cec2386e7778e3fb5d0a578e6f64290ceb5c687/shaders/sdf.js#L44-L51

Thanks again for this discussion.