Git Product home page Git Product logo

Comments (15)

behdad avatar behdad commented on July 1, 2024 8

Indeed, fwidth() of signed-distance is wrong. In GLyphy I use:

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

  vec4 color = vec4 (0,0,0,1); 

  float gsdist = glyphy_sdf (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); 
  float sdist = gsdist / m * u_contrast; 

where p is the texel coordinate.

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024 8

For most fonts I use 64x64 or sometimes 72x72 texels per glyph. This captures the details nicely, unless the font has very thin lines (like e.g. Parchment). When creating the texture, I use bin-packing to tightly pack the glyphs, so in practice they almost never need the full 72x72 texels.

I then render text by creating a mesh for the specified font size. This means that for a font size of 12pt, the mesh is actually smaller than 72x72 pixels per glyph and therefor scaling will occur. I want to be able to render 6pt text as well as 480pt text using the same font texture. Here's what it looks like when using a proper shader:

2016-09-30_205834

This is Calibri Light, stored on the texture as 72x72 texels (max) per glyph, then rendered at 15pts. I am not using multi-sampling, all anti-aliasing is done in the shader. Here's what it looks like when using float w = fwidth( sigDist ); float opacity = smoothstep( 0.5 - w, 0.5 + w, sigDist ) with float sigDist = median( sample.r, sample.g, sample.b ); as explained in the MSDFGEN docs:

2016-09-30_210113

When zooming in on the text, differences are less pronounced, but you can still see artifacts when using the simple shader:

2016-09-30_211009

These are gone when using the proper shader:

2016-09-30_211114

While frame rates are never a great way to compare performance, it's worth pointing out that both shaders run at 318 fps (NVIDIA GTX980M). I have also tested on iOS devices and performance is great there, too. So I'd really recommend using a slightly more complicated shader, because of the much higher quality.

Edit: just profiled using OpenGL queries and both shaders have very similar performance at roughly 1.7ms for the top-most image and 0.85ms for the zoomed-in image. It's worth noting that the mesh is recreated every frame and this is reflected in the timings as well. An optimized implementation might shave off a significant number of microseconds, I am sure.

from msdfgen.

Chlumsky avatar Chlumsky commented on July 1, 2024

That shader was just an example that can be used as a quick demonstration. If you're doing something serious you definitely shouldn't use the derivative of the signed distance but of the coordinate instead. The only problem is that then you need to keep track of the range of the distance field to correctly convert it to a 1 pixel thick smoothing. This is the same as with single channel distance fields, so you might be able to find more information about this elsewhere.

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024

Actually, I think you misunderstood, if I may be so bold. Taking the derivative of the signed distance is actually correct, as it tells you about the rate of change over the span of 1 pixel. In combination with the smoothstep function, this results in proper anti-aliasing. You can tell by the difference in quality in the second image. Anyway, I was merely suggesting to update the README.md to improve text rendering quality.

from msdfgen.

Chlumsky avatar Chlumsky commented on July 1, 2024

I'm not saying it is completely incorrect, just not exact. I'm pretty sure it's what causes the weird dots in the corners in your screenshots, but as you wish. Also, unless you're drawing the text in a 3D perspective, I would avoid derivatives completely.

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024

I believe the dots in the corners are caused by a relatively low size for the glyphs on the texture. The implementation I use (this one) reserves 32x32 texels by default, which is usually not enough to encode all font details properly. The dots are also visible in the top screenshot, that uses the default shader from the README.md file. Thanks for your reply and thanks again for sharing your code.

from msdfgen.

Chlumsky avatar Chlumsky commented on July 1, 2024

Still try it though, I bet you there won't be any dots if you get rid of fwidth (e.g. replace it with a constant).

from msdfgen.

rougier avatar rougier commented on July 1, 2024

@paulhoux The L letter seems to be better in your version but there are still dots in the corner of the I just before. Also, the hole in the H (third line) seems to have been accentuated.

@Chlumsky Why is there a hole in the H on third line but not on fourth line ?

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024

I stand corrected. I tried with an arbitrary constant (in my case 0.15) and the little dots disappeared. This means that taking the fwidth of the signed distance is wrong in a theoretical sense. Still, I do believe the minor changes I made to the shader code as presented in the README.md do result in better visual quality, even though they technically are incorrect.

From this article, I understand that the right thing to do would be to calculate the distance from the fragment to the glyph outline as measured in pixels. Then use float opacity = clamp( 0.5 - distanceInPixels, 0.0, 1.0 ); to calculate coverage. The problem is that we only know the distance in texels and we need to convert this value.

For what it's worth: the following shader code gave me higher quality results, even at small scales (zoomed out), but requires an arbitrary multiplication factor that I would like to get rid of:

vec3 sample = texture( uTex0, TexCoord ).rgb;
ivec2 sz = textureSize( uTex0, 0 );
float dx = dFdx( TexCoord.x ) * sz.x;
float dy = dFdy( TexCoord.y ) * sz.y;
float toPixels = 8.0 * inversesqrt( dx * dx + dy * dy );
float sigDist = median( sample.r, sample.g, sample.b ) - 0.5;
float opacity = clamp( sigDist * toPixels + 0.5, 0.0, 1.0 );

Also, when zooming out too far, texture sampling artifacts occur that render the text unreadable. I believe this could be solved by using more padding between glyphs or clamping texture coordinates.

On the left the shader using fwidth( sigDist ), on the right the shader mentioned in this post:
sdf_render_comparison
(Text rendered at 25%. Please disregard the layout of the text, it's a work in progress.)

from msdfgen.

Michaelangel007 avatar Michaelangel007 commented on July 1, 2024

fwidth() of signed-distance is wrong.

I respectively disagree -- it adds a bit of sharpening. I put together this (shadertoy) demo that clearly shows this:

https://www.shadertoy.com/view/llK3Wm#

I couldn't get the isotropic antialiasing variation to work. :-/

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024

I don't think your implementation is correct, @Michaelangel007 . The smoothstep version should not use the range [0.33, 0.66], because this is much too blurry - it should be way tighter. How much tighter is calculated by the fwidth() function, but as discussed in this issue it should be converted to pixel coordinates first, otherwise the results are clearly inferior. See the image I posted for a comparison.

FYI: the shader I ended up with differs from the one I posted. It is a bit more complicated, similar to:
http://www.essentialmath.com/blog/?p=151&cpage=1

from msdfgen.

Michaelangel007 avatar Michaelangel007 commented on July 1, 2024

I agree the default smoothstep() needs tweaking. You can hold down the mouse button and drag it left/right. At the far left it is equivalent to a = smoothstep( 0.5, 0.5, d ); which is sharp but then the edges aren't anti-alised.

Thanks for the link. I've added that to the references.

from msdfgen.

Michaelangel007 avatar Michaelangel007 commented on July 1, 2024

@paulhoux I've added your shader via SMOOTH_2.

Here is a picture of your shader: float w = fwidth( d ); a = smoothstep( 0.5 - w, 0.5 + w, d );
paul_fwidth_smoothstep

And here is the one using 1/fwidth() & clamp: float v = s / fwidth( s ); a = clamp( v + 0.5, 0.0, 1.0 );
michael_recip_fwidth_clamp

If you do a Photoshop Difference on the two images are almost identical. It is almost impossible to visually see any differences between the two.

from msdfgen.

paulhoux avatar paulhoux commented on July 1, 2024

Yes, but remember that this is still incorrect and actually has nothing to do with the discussion in this issue. Taking the fwidth from the texture sample is only part of the solution, as it does not take into account the scale of your glyph in pixels. You're not calculating the distance to the glyph's edge in pixels, but in texels, leading to poor quality rendering when you downscale the glyph. See the link in my previous reply for a proper solution.

@Chlumsky : I'd also recommend to close this issue, as it is no longer contributing anything to msdfgen.

from msdfgen.

Michaelangel007 avatar Michaelangel007 commented on July 1, 2024

I don't think anyone is saying the fwidth( texel ) is mathematically correct. What I am saying that it is certainly visually "good enough" -- the results speak for themselves compared to the proper partial derivatives of the texture coordinates.

In my day job I deal with OpenGL ES on smart TV's and shader complexity is a real ongoing issue -- any wins in optimizations here are VERY noticeable.

In practice there most certainly is a difference between:

  • mathematically correct but slow, and
  • close enough but very fast.

Truth be told, most of graphics is "one big hack", or I should say "approximations" anyways.

leading to poor quality rendering when you downscale the glyph.

In practice I haven't found that to be an issue at all. What size is your original SDF font and how small are you down sampling that you found this to be problematic?

I used to use 2 shaders:

  • one for downscaling, and
  • one for upscaling.

Using the shader I presented above lets me unify them and avoid unnecessary shader loading without general loss of quality for both upscaling and downscaling. Not everyone is running SDF fonts on a discrete GPU that have spare horsepower to burn. :-/

Granted I'm using a SDF font of 42 px so that I can have both downsampling and upsampling looking beautiful -- maybe this is overkill and I could get away with 32 px? I do know that upsampling to 224 px looks great, and downsampling to 12px looks great (which is sufficient for our needs.)

Are there any heuristics for the ratio of the SDF glyphs to actual rendered font height that people are aware of?

from msdfgen.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.