I'm trying to render a freetype font using OpenGL, following the example posted at http://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02.
I've been able to generate a texture atlas from the font, creating shaders and creating quads. What I seem to get stuck at is passing the texture to the shader and/or getting the correct UVs for my quads. Been struggling for a good while now and really need the help.
The following is the struct I use to create my texture atlas.
struct FontCharacter
{
float advanceX;
float advanceY;
float bitmapWidth;
float bitmapHeight;
float bitmapLeft;
float bitmapTop;
float uvOffsetX;
float uvOffsetY;
};
struct FontTextureAtlas
{
GLuint texture;
GLuint textureUniform;
int width;
int height;
FontCharacter characters[128];
FontTextureAtlas(FT_Face face, int h, GLuint tUniform)
{
FT_Set_Pixel_Sizes(face, 0, h);
FT_GlyphSlot glyphSlot = face->glyph;
int roww = 0;
int rowh = 0;
width = 0;
height = 0;
memset(characters, 0, sizeof(FontCharacter));
for (int i = 32; i < 128; i++)
{
if (FT_Load_Char(face, i, FT_LOAD_RENDER))
{
std::cout << "Loading character %c failed\n", i;
continue;
}
if (roww + glyphSlot->bitmap.width + 1 >= MAX_WIDTH)
{
width = std::fmax(width, roww);
height += rowh;
roww = 0;
rowh = 0;
}
roww += glyphSlot->bitmap.width + 1;
rowh = std::fmax(rowh, glyphSlot->bitmap.rows);
}
width = std::fmax(width, roww);
height += rowh;
glGenTextures(1, &texture);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glGenTextures failed\n";
}
glActiveTexture(GL_TEXTURE0 + texture);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glActiveTexture failed\n";
}
glBindTexture(GL_TEXTURE_2D, texture);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glBindTexture failed\n";
}
glUniform1i(tUniform, 0);
textureUniform = tUniform;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glTexImage2D failed\n";
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glPixelStorei failed\n";
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glTexParameteri failed\n";
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glTexParameteri failed\n";
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glTexParameteri failed\n";
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "glTexParameteri failed\n";
}
int ox = 0;
int oy = 0;
rowh = 0;
for (int i = 32; i < 128; i++)
{
if (FT_Load_Char(face, i, FT_LOAD_RENDER))
{
std::cout << "Loading character %c failed\n", i;
continue;
}
if (ox + glyphSlot->bitmap.width + 1 >= MAX_WIDTH)
{
oy += rowh;
rowh = 0;
ox = 0;
}
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, glyphSlot->bitmap.width, glyphSlot->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, glyphSlot->bitmap.buffer);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "BORKED AGAIN\n";
}
characters[i].advanceX = glyphSlot->advance.x >> 6;
characters[i].advanceY = glyphSlot->advance.y >> 6;
characters[i].bitmapWidth = glyphSlot->bitmap.width;
characters[i].bitmapHeight = glyphSlot->bitmap.rows;
characters[i].bitmapLeft = glyphSlot->bitmap_left;
characters[i].bitmapTop = glyphSlot->bitmap_top;
characters[i].uvOffsetX = ox / (float)width;
characters[i].uvOffsetY = oy / (float)height;
rowh = std::fmax(rowh, glyphSlot->bitmap.rows);
ox += glyphSlot->bitmap.width + 1;
}
std::cout << "Generated a " << width << "x " << height << " (" << width * height / 1024 << " kb) texture atlas.\n";
}
~FontTextureAtlas()
{
glDeleteTextures(1, &texture);
}
Local variables and function heads used in the renderer
class RenderCore
{
FT_Library library;
FT_Face face;
FontTextureAtlas* a48;
FontTextureAtlas* a24;
FontTextureAtlas* a12;
GLuint vbo;
GLuint vao;
GLuint m_posUV;
GLuint m_colorIN;
GLuint m_texture;
int InitFT();
void RenderText(const char* text, FontTextureAtlas* atlas, float x, float y, float sx, float sy);
}
This is where I load my fonts.
int RenderCore::InitFT()
{
if (FT_Init_FreeType(&library))
{
std::cout << "Could not Initialize freetype library.\n";
return 0;
}
/* Load a font */
if (FT_New_Face(library, "assets/Fonts/arialbd.ttf", 0, &face))
{
std::cout << "Could not open font assets/Fonts/DentonBeta2.ttf\n";
return 0;
}
m_shaderManager->CreateProgram("Text");
m_shaderManager->LoadShader("shaders/Text.vertex", "TextVS", GL_VERTEX_SHADER);
m_shaderManager->LoadShader("shaders/Text.fragment", "TextFS", GL_FRAGMENT_SHADER);
m_shaderManager->AttachShader("TextVS", "Text");
m_shaderManager->AttachShader("TextFS", "Text");
m_shaderManager->LinkProgram("Text");
m_shaderManager->UseProgram("Text");
m_shaderManager->UseProgram("Text");
m_colorIN = m_shaderManager->GetUniformLocation("Text", "inputColor");
m_texture = m_shaderManager->GetUniformLocation("Text", "texture");
// Create the vertex buffer object
glGenBuffers(1, &vbo);
glGenVertexArrays(1, &vao);
/* Create texture atlasses for several font sizes */
a48 = new FontTextureAtlas(face, 48, m_texture);
a24 = new FontTextureAtlas(face, 24, m_texture);
a12 = new FontTextureAtlas(face, 12, m_texture);
}
Rendering function.
void RenderCore::RenderText(const char* text, FontTextureAtlas* atlas, float x, float y, float sx, float sy)
{
m_shaderManager->UseProgram("Text");
const unsigned char* p;
std::vector<glm::vec4> coords;
int c = 0;
for (p = (const unsigned char*)text; *p; p++)
{
float x2 = x + atlas->characters[*p].bitmapLeft * sx;
float y2 = -y - atlas->characters[*p].bitmapTop * sy;
float w = atlas->characters[*p].bitmapWidth * sx;
float h = atlas->characters[*p].bitmapHeight * sy;
x += atlas->characters[*p].advanceX * sx;
y += atlas->characters[*p].advanceY * sy;
if (!w || !h)
continue;
coords.push_back(
glm::vec4(
x2,
-y2,
atlas->characters[*p].uvOffsetX,
atlas->characters[*p].uvOffsetY)
);
coords.push_back(
glm::vec4(
x2 + w,
-y2,
atlas->characters[*p].uvOffsetX + atlas->characters[*p].bitmapWidth / atlas->width,
atlas->characters[*p].uvOffsetY)
);
coords.push_back(
glm::vec4(
x2,
-y2 - h,
atlas->characters[*p].uvOffsetX,
atlas->characters[*p].uvOffsetY + atlas->characters[*p].bitmapHeight / atlas->height)
);
coords.push_back(
glm::vec4(
x2 + w,
-y2,
atlas->characters[*p].uvOffsetX + atlas->characters[*p].bitmapWidth / atlas->width,
atlas->characters[*p].uvOffsetY)
);
coords.push_back(
glm::vec4(
x2,
-y2 - h,
atlas->characters[*p].uvOffsetX,
atlas->characters[*p].uvOffsetY + atlas->characters[*p].bitmapHeight / atlas->height)
);
coords.push_back(
glm::vec4(
x2 + w,
-y2 - h,
atlas->characters[*p].uvOffsetX + atlas->characters[*p].bitmapWidth / atlas->width,
atlas->characters[*p].uvOffsetY + atlas->characters[*p].bitmapHeight / atlas->height)
);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glActiveTexture(GL_TEXTURE0 + atlas->texture);
glUniform1i(atlas->textureUniform, 0);
glBindTexture(GL_TEXTURE_2D, atlas->texture);
m_shaderManager->SetUniform(1, glm::vec4(0, 0, 1, 1), m_colorIN);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, coords.size() * sizeof(glm::vec4), coords.data(), GL_DYNAMIC_DRAW);
//Generate VAO
glBindVertexArray(vao);
//Position
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)0);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, coords.size());
glDisableVertexAttribArray(0);
m_shaderManager->ResetProgram();
}
Vertex shader
#version 440
in vec4 pos_uv;
out vec2 uv;
void main()
{
gl_Position = vec4(pos_uv.xy, 0, 1);
uv = pos_uv.zw;
}
Fragment shader
#version 440
in vec2 uv;
uniform sampler2D texture;
uniform vec4 inputColor;
out vec4 color;
void main()
{
color = vec4(inputColor.rgb, texture2D(texture, uv).a);
}
Using gDebugger I can see the texture atlas having been generated properly and the VBO seems fine as well. The result is just a bunch of squares on screen however and I really have no idea why. I think it might be a problem with passing the texture to the shader, all channels except the alpha channel is empty and the alpha is always 1.
Font rasterization is the process of converting text from a vector description (as found in scalable fonts such as TrueType fonts) to a raster or bitmap description. This often involves some anti-aliasing on screen text to make it smoother and easier to read.
Text rendering is the process of converting a string to a format that is readable to the user. For simple scripts, this process is straightforward. For more complicated scripts, there are many factors that lead to the correct rendering of a string.
Managed to solve the problem, instead of glActiveTexture(GL_TEXTURE0 + texture); it should have only been glActiveTexture(GL_TEXTURE0);
I'm assuming glActiveTexture binds to a specific index within a program and not for all textures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With