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)
{
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.
Cheers,
NightDreamer
EDIT:
- minor changes in question 1 and 3