Text not rendering correctly - OpenGL using FreeType2
Asked Answered
S

2

4

Almost exact duplicate question: OpenGL font rendering using Freetype2.

I am trying to render text in my OpenGL program using FreeType2 (2.5.3), based on this tutorial: http://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02, using the same structs as Bentebent did in his question (above).

I am having the same problem he was having with rectangles showing up instead of legible characters (even using the correct GL_TEXTURE0 parameter for glActiveTexture())

For example, rendering green strings "Hello" and "World" below it gives me this:

Rendering 'Hello' and 'World'

As with Bentebent, using gDebugger I can see that my textures are generated fine. I have scoured google/stackoverflow, tried different parameters for glTexImage2D, different formulas for color in my fragment shader, etc, but no luck. Been stuck for a while now. Any help is appreciated.

Structs to create texture atlas:

//DrawTestOpenGLWnd.h
struct FontCharacter
{
    float advanceX;
    float advanceY;

    float bitmapWidth;
    float bitmapHeight;

    float bitmapLeft;
    float bitmapTop;

    float uvOffsetX;
    float uvOffsetY;
};

struct FontTextureAtlas
{
    GLuint texture;
    GLint textureUniform;

    int width;
    int height;

    FontCharacter characters[128];

    FontTextureAtlas(FT_Face face, int h, GLint 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))
            {
                TRACE("Loading character %c failed\n", i);
                continue;
            }

            if (roww + glyphSlot->bitmap.width + 1 >= MAX_WIDTH)
            {
                width = std::max(width, roww);
                height += rowh;
                roww = 0;
                rowh = 0;
            }

            roww += glyphSlot->bitmap.width + 1;
            rowh = std::max(rowh, glyphSlot->bitmap.rows);
        }

        width = std::max(width, roww);
        height += rowh;

        glGenTextures(1, &texture);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glGenTextures failed\n");
        }

        glActiveTexture(GL_TEXTURE0);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glActiveTexture failed\n");
        }

        glBindTexture(GL_TEXTURE_2D, texture);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glBindTexture failed\n");
        }

        glUniform1i(tUniform, 0);
        textureUniform = tUniform;

        glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glTexImage2D failed\n");
        }

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glPixelStorei failed\n");
        }

        glPixelStorei(GL_PACK_ALIGNMENT, 1);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glPixelStorei failed\n");
        }

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glTexParameteri failed\n");
        }

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glTexParameteri failed\n");
        }

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        if (glGetError() != GL_NO_ERROR){
            TRACE("glTexParameteri failed\n");
        }

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        if (glGetError() != GL_NO_ERROR){
            TRACE("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)) {
                TRACE("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) {
                TRACE("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::max(rowh, glyphSlot->bitmap.rows);
            ox += glyphSlot->bitmap.width + 1;
        }

        TRACE("Generated a %d x %d (%d kb) texture atlas.\n", width, height, width * height / 1024);
    }

    ~FontTextureAtlas()
    {
        glDeleteTextures(1, &texture);
    }
};

InitFreeType function:

void DrawTestOpenGLWnd::InitFreeType(char * strFontFilePath)
{

    m_error = FT_Init_FreeType(&m_library);
    if (m_error) {
        TRACE("An error occurred during library initialization");
    }

    m_error = FT_New_Face(m_library, strFontFilePath, 0, &m_face);
    if (m_error == FT_Err_Unknown_File_Format) {
        TRACE("Font file could be opened and read, but it appears that its font format is unsupported");
    }
    else if (m_error) {
        TRACE("Font file could not be opened or read. Or it's broken.");
    }

    m_program_text = LoadShaders("TextVertexShader.vertexshader", "TextFragmentShader.fragmentshader");
    glUseProgram(m_program_text);

    m_uniform_texture = glGetUniformLocation(m_program_text, "texture");
    m_uniform_textColor = glGetUniformLocation(m_program_text, "textColor");

    glGenVertexArrays(1, &vao_text);
    glBindVertexArray(vao_text);

    glGenBuffers(1, &vbo_text);
    //glBindBuffer(GL_ARRAY_BUFFER, vbo_text);

    a48 = new FontTextureAtlas(m_face, 48, m_uniform_texture);
    a24 = new FontTextureAtlas(m_face, 24, m_uniform_texture);
    a12 = new FontTextureAtlas(m_face, 12, m_uniform_texture);
}

RenderText function:

void DrawTestOpenGLWnd::RenderText(char * text, FontTextureAtlas * atlas, float x, float y, float sx, float sy)
{
    glUseProgram(m_program_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)
            );
    }

    glBindVertexArray(vao_text);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glActiveTexture(GL_TEXTURE0);
    glUniform1i(atlas->textureUniform, 0);
    glBindTexture(GL_TEXTURE_2D, atlas->texture);

    GLfloat textColor[4] = {0.0, 1.0, 0.0, 0.}; //green
    glUniform4fv(m_uniform_textColor, 1, textColor);

    glBindBuffer(GL_ARRAY_BUFFER, vbo_text);
    glBufferData(GL_ARRAY_BUFFER, coords.size() * sizeof(glm::vec4), coords.data(), GL_DYNAMIC_DRAW);

    //Position
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)0);

    glDrawArrays(GL_TRIANGLES, 0, coords.size());

    glDisableVertexAttribArray(0);

    glBindVertexArray(0);
    glUseProgram(0);
}

TextVertexShader.vertexshader:

#version 330 core

layout(location = 0) in vec4 pos_uv;
out vec2 uv;

void main(void) {
  gl_Position = vec4(pos_uv.xy, 0, 1);
  uv = pos_uv.zw;
}

TextFragmentShader.fragmentshader:

#version 330 core

in vec2 uv;
uniform sampler2D texture;
uniform vec4 textColor;

out vec4 color;

void main(void) {
  color = vec4(textColor.rgb, texture2D(texture, uv).a);
}
Shonna answered 11/12, 2014 at 19:25 Comment(2)
This example looks promising: github.com/rougier/freetype-gl More generic question: #8848399Pontifical
What are you talking about? This font rendering looks fine!Poinciana
D
4

Your texture format is R8, so it contains R channel only. Your shader seams to use A channel. Compare these 2 lines of code:

glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, 
             GL_UNSIGNED_BYTE, 0);
 ...
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, glyphSlot->bitmap.width,
                glyphSlot->bitmap.rows, GL_RED, ...

vs

color = vec4(textColor.rgb, texture2D(texture, uv).a);

I'd suggest to change GL_R8 & GL_RED to GL_ALPHA8 & GL_ALPHA if your OpenGL < 3.0 or change texture2D(texture, uv).a to texture2D(texture, uv).r otherwise.

Drugi answered 11/12, 2014 at 19:31 Comment(3)
Right! Thank you so much! Stupid mistake on my part. The tutorial used GL_ALPHA, which is invalid (I'm using OpenGL 3.3), so when I changed glTexImage2D and glTexSubImage2D to use GL_RED I forgot about my shader. I changed my shader to texture2D(texture, uv).r and the text renders correctly.Shonna
@Shonna what do you mean with "GL_ALPHA is invalid" ?Inch
@Inch GL_ALPHA is & was valid but sampling such channel from texture not containing alpha channel simply returns 0.0Drugi
S
3

OP here. This is my working code, as per Anonymous' suggestion:

In struct FontCharacterAtlas:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, 
             GL_UNSIGNED_BYTE, 0);
 ...
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, glyphSlot->bitmap.width, glyphSlot->bitmap.rows, 
                GL_RED, GL_UNSIGNED_BYTE, glyphSlot->bitmap.buffer);

In TextFragmentShader.fragmentshader:

color = vec4(textColor.rgb, texture2D(texture, uv).r);

Text renders, albeit not very nicely (some characters' sides are slightly cut off, and when using a24 or a12 atlas the font is quite messy) but that's another story.

Shonna answered 11/12, 2014 at 20:37 Comment(2)
Since your texture filter is GL_LINEAR, you should ensure that you pack your glyphs with a 1 texel border on all sides to avoid leaking from adjacent glyphs (the GL_LINEAR filter samples the 4 nearest texels and uses a weighted average). This will resolve most issues that only appear when using GL_LINEAR filtering and not GL_NEAREST. Judging from the tutorial you followed, it is trying to tightly pack the glyphs and that's not appropriate for GL_LINEAR filtering.Ohl
@AndonM.Coleman Thanks for your input. How would I go about packing the glyphs with a 1 texel border?Shonna

© 2022 - 2024 — McMap. All rights reserved.