Sign in to follow this  

Signed Distance Field Text Rendering: some questions

This topic is 663 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello everyone smile.png

 

I've just implemented signed distance field text rendering and I've got a few minor problems, I can't solve by myself. wacko.png

 

First of all, a quick overview of the problems:

  • I advance the cursor on the x-axis with the value of "xAdvance" (should be self-explanatory), but the space between two characters seems to be wrong (see screenshots). How can I fix this?
  • How could I set a font size like "Set this font to a size of 12px"? => How do I calculate the correct box size for a single character?
  • It occurs that the visual font thickness depends on the background color (see screenshots, in the upper one, the font seems to be clearly thicker). Why does it happen? (Due to alpha?)  And how can I prevent this behaviour?

So, here are some screenshots:

 

tQjtDDi.png

 

5YqB0wp.png

 

Now, I hope you see what I meant. If not feel free to ask and I will try to clarify it.

Here, my implementation:

 

The signed distance field font files were created with Hiero

It created the texture (512x512 in my case) and a .fnt text file.

The important (and almost all) parts (/lines) of this file are like

char id=33    x=484  y=202  width=24   height=60   xoffset=-7   yoffset=7    xadvance=33

where x,y,width,height represent non normalised texture coordinates and the offsets and xadvance represent pixel numbers.

 

The fragment shader, which calculates the actual visual character on the signed distance field texture:

#version 330 core

in vec2 _uv;

uniform vec3 color;
uniform sampler2D fontAtlas;

const float width = 0.5;
const float edge = 0.1;

void main()
{
	float distance = 1.0 - texture(fontAtlas, _uv).a;
	float alpha = 1.0 - smoothstep(width, width + edge, distance);
	gl_FragColor = vec4(color, alpha);
}

This is how I calculate the vertices for drawing:

void Font::write(const char* text, float scale, float x, float y, float z)
{
	for (const char* p = text; *p; p++)
	{

		Glyph g = m_glyphs[*p];

		float gx = x + static_cast<float>(g.xOffset) * scale;
		float gy = y - static_cast<float>(g.yOffset) * scale;
		float gw = static_cast<float>(g.width) * scale;
		float gh = static_cast<float>(g.height) * scale;

		x += g.xAdvance * scale;

		if (!gw || !gh)
			continue;

		float u1 = static_cast<float>(g.x) / static_cast<float>(m_atlas.width);
		float u2 = u1 + (static_cast<float>(g.width) / static_cast<float>(m_atlas.width));
		float v1 = static_cast<float>(m_atlas.height - g.y) / static_cast<float>(m_atlas.height);
		float v2 = v1 - (static_cast<float>(g.height) / static_cast<float>(m_atlas.height));

		m_buffer.push_back(Vertex(gx	 , gy	  , z, u1, v1));
		m_buffer.push_back(Vertex(gx	 , gy - gh, z, u1, v2));
		m_buffer.push_back(Vertex(gx + gw, gy - gh, z, u2, v2));
		m_buffer.push_back(Vertex(gx	 , gy	  , z, u1, v1));
		m_buffer.push_back(Vertex(gx + gw, gy - gh, z, u2, v2));
		m_buffer.push_back(Vertex(gx + gw, gy	  , z, u2, v1));
	}
}

This is how I render all of this:

void Font::render(const glm::mat4& ortho, const glm::vec3& color /*= glm::vec3(1.0f)*/)
{
	if (m_buffer.size() > 0)
	{
		m_shader.bind();
		m_shader.setUniform("ortho", ortho);
		m_shader.setUniform("color", color);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, m_atlas.id);
		glBindVertexArray(m_vao);
		glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * m_buffer.size(), &m_buffer[0], GL_STREAM_DRAW);

		glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(m_buffer.size()));

		glBindTexture(GL_TEXTURE_2D, 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
		glDisable(GL_BLEND);
			
		m_shader.unbind();
		m_buffer.clear();
	}
}

If you need some other code, just say it and I'll provide it. smile.png

 

Cheers,

NightDreamer

 

EDIT:

- minor changes in question 1 and 3

Edited by N1ghtDr34m3r

Share this post


Link to post
Share on other sites
  • I advance the cursor on the x-axis with the value of "xAdvance" (should be self-explanatory), but the space between two characters seems to be wrong (see screenshots). How can I fix this?

When you create signed distance field fonts, there's an extra amount of fuzziness around each character. It looks like in the hiero gui it's defined as 'spread' and you need to add an amount of padding to match the spread value. You don't mention that you're setting spread or padding in your config file, so I presume it's just taking a default value. Basically, you need to remove whatever spread/padding value that's being added from the xadvance to sort out the spacing.

 

  • It occurs that the visual font thickness depends on the background color (see screenshots, in the upper one, the font seems to be clearly thicker). Why does it happen? (Due to alpha?)  And how can I prevent this behaviour?

 

I think that might just be an optical illusion. Some sort of drop shadow (easily implemented by rendering a dark, offset version of the text before the light text), or an outline/outer glow (best implemented in the fragment shader - it's one of the strengths of SDFs) would probably sort it out.

Share this post


Link to post
Share on other sites

Hello C0lumbo,

 

thanks for replying. smile.png

 

 

 

  • I advance the cursor on the x-axis with the value of "xAdvance" (should be self-explanatory), but the space between two characters seems to be wrong (see screenshots). How can I fix this?

When you create signed distance field fonts, there's an extra amount of fuzziness around each character. It looks like in the hiero gui it's defined as 'spread' and you need to add an amount of padding to match the spread value. You don't mention that you're setting spread or padding in your config file, so I presume it's just taking a default value. Basically, you need to remove whatever spread/padding value that's being added from the xadvance to sort out the spacing.

Thanks for that. If I substract the spread from xAdvance it seems to be alright.

Anyway, sometimes it looks not so perfect, but I think, this is due to no kerning yet.

Any idea on how to implement it? I've got the data for a simple alphabet (something around 1111 kerning values), but don't know how to iterate it fast enough on a per character basis.

 

 

 

  • It occurs that the visual font thickness depends on the background color (see screenshots, in the upper one, the font seems to be clearly thicker). Why does it happen? (Due to alpha?)  And how can I prevent this behaviour?

 

I think that might just be an optical illusion. Some sort of drop shadow (easily implemented by rendering a dark, offset version of the text before the light text), or an outline/outer glow (best implemented in the fragment shader - it's one of the strengths of SDFs) would probably sort it out.

Unfortunately it is no illusion. rolleyes.gif

Here some screenshot, zoomed in a little bit:

fGeL1Z6.png?1

 

It is the same font with the same size but without a different color in the background.

Why does it occur? Is my fragment shader wrong?

 

Thanks and a good evening,

NightDreamer

Share this post


Link to post
Share on other sites

One of the gotchas with signed distance field fonts is the glyph rendering coordinate system.

 

The coordinates have to be floats.

 

Rendering a glyph at 10,10 does not produce the same results as 10.1f, 10.0f

 

Make sure your xAdvance is a float and you never truncate the coordinates to ints.

Share this post


Link to post
Share on other sites

Have you tried using alpha testing instead of alpha blending?  It looks like there is still light red around the second image (the white background), it's just less noticeable because the background is lighter.  If you're using bilinear filtering on the font, then the alpha will get softened out around the edges of the glyph, so along with the blending will produce what you're seeing.  If you use alpha testing, it will create a hard edge instead based on some absolute value.  Using point sampling would probably have the same result, but alpha testing would let you control the glyph size by increasing/decreasing the alpha cutoff (since with bilinear filtering, the edges around the glyph will blend from 0-1).

Edited by xycsoscyx

Share this post


Link to post
Share on other sites

Helloy xycsoscyx,

 

that's a point I've forgot to mention:

since I'm using OpenGL 3.3 Core, I can't use alpha testing by using glEnable(GL_ALPHA) and glAlphaFunc(...).

Is there some other way for that?

Share this post


Link to post
Share on other sites

Thanks for that, xycsoscyx.

But it occurs, that if I use alpha testing and discard it, the smooth outline of signed distance field won't be smooth anymore, due to the fact, that the smoothness (like antialiasing) relies on the alpha fading.

Any other ideas?

Edited by N1ghtDr34m3r

Share this post


Link to post
Share on other sites

Discard is the thing to use.

 

The thing to remember is the texture sampler is handling the blending for you.

 

Say you have two pixels side by side in your font. One is black and one is white.

 

When they get scaled to 4 pixels you will have 4 alpha values 0,0f.33f,0.66f,1.0f   (at a guess, depends on a lot of things, but it illustrates what I mean)

 

So when you do this in your pixel shader.

 

     if (alpha<0.5)
        discard;

 

You are doing the sharp edge you want.

Share this post


Link to post
Share on other sites

If you take the screenshot you posted and play around with the color balance (reduce green/blue), you'll see that there's still a red border around the "thinner" font, it's just lighter because of the alpha blending.  With the black background it's more prominent, but it's still there with the white background.  You could play around with the blend modes or the alpha cutoff of the font to tweak it to your liking.  Right now you have a very large soft border (a couple pixels it looks like), but if you make it a sharper cutoff (or discard when alpha < .25 or something, but still keep the alpha blending) then the font will start to look the same regardless of the background, while still keeping a soft (just thinner) border around the glyphs.  It will still vary a bit depending on what you're blending the font over, but it will be less prominent than what you currently have.  Playing around with the blend modes can change how it looks too, but blending is always part of one, the rest of another, so it will always vary based on what you are blending over.

Edited by xycsoscyx

Share this post


Link to post
Share on other sites

This topic is 663 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this