• 13
• 18
• 19
• 27
• 10

# Signed Distance Field Text Rendering: some questions

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

## Recommended Posts

Hello everyone

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

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:

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)
{

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_buffer.clear();
}
}


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

Cheers,

NightDreamer

EDIT:

- minor changes in question 1 and 3

Edited by N1ghtDr34m3r

##### 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?

• 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 on other sites

Hello C0lumbo,

• 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?

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.

Here some screenshot, zoomed in a little bit:

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 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 on other sites

Hello Stainless,

that doesn't changed anything and wasn't a problem.

Anyway, two of the three questions aren't answered by now.

##### 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 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 on other sites

Are you using pixel shaders for the text rendering?  It seems like the only way now is to use the discard operation if the alpha is below a threshold.

http://stackoverflow.com/questions/24302152/opengl-alpha-test-how-to-replace-alphafunc-deprecated

##### 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 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)