Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: parameterize min glyph spacing #11

Closed
Phildo opened this issue Dec 20, 2020 · 7 comments
Closed

Feature request: parameterize min glyph spacing #11

Phildo opened this issue Dec 20, 2020 · 7 comments

Comments

@Phildo
Copy link

Phildo commented Dec 20, 2020

i'm using mtsdf and drawing outlines around my text, but the outlines from adjacent characters is bleeding in very slightly around the borders of some of my characters. if there were a way to set the glyphs to not be packed so tight (ie "leave an additional 2px border around each character") I would expect that could solve it!

@Chlumsky
Copy link
Owner

A sufficient border for this exact purpose is already included. When used correctly, there is absolutely no possibility of this happening. I can help you figure out what you're doing wrong but there is no reason to add the feature you requested.

@Phildo
Copy link
Author

Phildo commented Dec 20, 2020

interesting- thanks for the response!

I don't quite understand, though. in the extreme example, lets say you're rendering the font at approximately the texture resolution (lets say 80px/glyph). if you want a 100px border (using the alpha channel of mtsdf texture), how can that be accomplished?

NOTE: don't worry about scouring this huge information dump- I just figured it's better to include more info than less. :)

This is what the issue looks like in my case. See the right side of the 'k's, you can see a sliver of white from the adjacent glyph ('l').

font

I noticed when I bring the mesh very close to the camera, the artifact actually goes away. So this is it from a bit of distance (zoom in to see the artifact). In my case, the artifact is very visible when viewed through a Valve Index HMD.

k

k_wire

Each glyph is 5 quads, one which minimally fits the character, and 4 extending the quad to the height of the row and the width of its spacing (so I can have black text with a thin white outline on a red background).

meshquads

Here's my code that constructs the mesh (per character):

The vertexs are a vec2 for position, and a vec2 for UV. The "FontGlyph" struct just has "pl,pb,pr,pt" which are "place {left,bottom,right,top}", and correspond to where the glyph quad should be placed relative to the cursor and baseline, and "access {left,bottom,right,top}", aka its UVs. (and size(height) just multiplies the "place" values).

void DoRendererMenu::setChar(FontGlyph *charLut, char c, glm::vec2 key_p, float cursor, float key_h, float *mesh_x, float *cursor_x, CharVertexBufferObject *vertexs)
{
  FontGlyph lut = charLut[c];
  FontGlyph elut = charLut[' '];
  float char_s = key_h*0.8;
  lut.size(char_s);
  float gw = lut.pr-lut.pl;
  float gh = lut.pt-lut.pb;
  float gl = cursor;
  float gr = cursor+gw;
  float gb = key_p.y-key_h*0.9+lut.pb;
  if(gb < key_p.y-key_h) gb = key_p.y-key_h;
  float gt = gb+gh;
  *cursor_x = cursor+lut.advance;
  float new_meshx = gr+(*cursor_x-gr)/2.0;
  *mesh_x = new_meshx;

  //top
  vertexs[0]  = {glm::vec2(key_p.x,  key_p.y),       glm::vec2(elut.al,elut.at)};
  vertexs[1]  = {glm::vec2(new_meshx,key_p.y),       glm::vec2(elut.ar,elut.at)};
  vertexs[2]  = {glm::vec2(gl       ,gt),            glm::vec2(elut.al,elut.ab)};
  vertexs[3]  = {glm::vec2(gr       ,gt),            glm::vec2(elut.ar,elut.ab)};
  //left
  vertexs[4]  = {glm::vec2(key_p.x  ,key_p.y),       glm::vec2(elut.al,elut.at)};
  vertexs[5]  = {glm::vec2(gl       ,gt),            glm::vec2(elut.ar,elut.at)};
  vertexs[6]  = {glm::vec2(key_p.x  ,key_p.y-key_h), glm::vec2(elut.al,elut.ab)};
  vertexs[7]  = {glm::vec2(gl       ,gb),            glm::vec2(elut.ar,elut.ab)};
  //glyph
  vertexs[8]  = {glm::vec2(gl,gt),                   glm::vec2(lut.al,lut.at)};
  vertexs[9]  = {glm::vec2(gr,gt),                   glm::vec2(lut.ar,lut.at)};
  vertexs[10] = {glm::vec2(gl,gb),                   glm::vec2(lut.al,lut.ab)};
  vertexs[11] = {glm::vec2(gr,gb),                   glm::vec2(lut.ar,lut.ab)};
  //right
  vertexs[12] = {glm::vec2(gr       ,gt     ),       glm::vec2(elut.al,elut.at)};
  vertexs[13] = {glm::vec2(new_meshx,key_p.y),       glm::vec2(elut.ar,elut.at)};
  vertexs[14] = {glm::vec2(gr       ,gb           ), glm::vec2(elut.al,elut.ab)};
  vertexs[15] = {glm::vec2(new_meshx,key_p.y-key_h), glm::vec2(elut.ar,elut.ab)};
  //bottom
  vertexs[16] = {glm::vec2(gl       ,gb     ),       glm::vec2(elut.al,elut.at)};
  vertexs[17] = {glm::vec2(gr       ,gb     ),       glm::vec2(elut.ar,elut.at)};
  vertexs[18] = {glm::vec2(key_p.x  ,key_p.y-key_h), glm::vec2(elut.al,elut.ab)};
  vertexs[19] = {glm::vec2(new_meshx,key_p.y-key_h), glm::vec2(elut.ar,elut.ab)};
}

and here's the shader I use:

#version 450
#extension GL_KHR_vulkan_glsl : enable
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_multiview : enable
layout(set = 0, binding = 2) uniform sampler2D colorSampler;
layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec3 fragNormal;
layout(location = 0) out vec4 outColor;
void main()
{
  vec4 s = texture(colorSampler,fragUV);
  float bsd = max(min(s.r,s.g), min(max(s.r,s.g), s.b));
  float wsd = s.a;
  float omega = 5.0;
  float black = clamp(omega*(bsd-0.5)+0.5, 0.0, 1.0);
  omega = 1.0;
  float white = clamp(omega*(wsd-0.5)+0.5, 0.0, 1.0);
  float omblack = 1.0-black;
  float a = max(white,black);
  outColor = vec4(mix(vec3(0.8,0.1,0.1),vec3(omblack,omblack,omblack),a),0.8+(a*0.2));
}

If there's anything weird about how I'm approaching this, I'd love to be pointed in a more sensible direction! Thanks for your help, and for your work on this library :)

@Phildo
Copy link
Author

Phildo commented Dec 20, 2020

oh wow I'm just realizing that this might be a mipmapping issue. let me test that real quick

@Chlumsky
Copy link
Owner

That's quite likely if the artifact disappears when the camera is closer. Mipmapping should never be used with distance field textures as it doesn't help. However you need to increase the distance range significantly to be able to achieve anti-aliasing for minification. Also, looking at your texture, it is generated incorrectly due to overlapping parts in the font, so I would strongly recommend the latest version with Skia geometry preprocessing.

@Phildo
Copy link
Author

Phildo commented Dec 21, 2020

Would you mind expanding on what you mean by "you need to increase the distance range significantly to be able to achieve anti-aliasing for minification"? Also, I've updated to the latest (1.1), and will try with that, but where do you see "overlapping parts in the font"?

Thanks for your help!

@Chlumsky
Copy link
Owner

Chlumsky commented Dec 21, 2020

To be able to achieve anti-aliasing using the signed distance retrieved from the distance field, you need to have a range of distances covering at least two screen-space pixels. Assuming the texture you posted is the one you're using, it is very much insufficient for this purpose at the minification level in your screenshots. You can increase the distance range coverage with the -pxrange and -emrange arguments. Specifically, pxrange should be at least 2/s where s is the lowest display scale (on-screen size / texture size). I'd say the texture is also about 4 times bigger than it needs to be but that's beside the point (although it can become a problem as a larger texture size requires a greater pxrange and at pxrange around 128 the smooth gradient starts breaking down in a typical 8-bit color encoding).

As for the overlapping parts, I mean self-intersecting paths. You can see that there are certain strange indentations near some corners in the generated atlas, which are due to the geometry of the glyphs being composed of overlapping segments, which is something that requires additional preprocessing. That was added in the latest version with the help of the Skia library. Before that, fonts like these were simply not supported (the preprocessing can be performed in a font editor software instead). Proper fonts have these self-intersections resolved and do not cause this issue. Sometimes it happens with certain accents or extended character sets like CJK, but this is the first time I've seen this with the base ASCII character set. A good example is the ampersand character in your image. Normally, it would be composed of three contours, seen below as red, green, and blue, but in your font, it is presumably realized as a single self-intersecting contour (bottom right).

ampersands

@Phildo
Copy link
Author

Phildo commented Dec 21, 2020

ah that's so helpful! thank you so much. issue closed

@Phildo Phildo closed this as completed Dec 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants