0
\$\begingroup\$

I am currently implementing font rendering into my 3D game engine using the FreeType library. I have followed this tutorial: https://learnopengl.com/In-Practice/Text-Rendering, which lead into this next tutorial: https://learnopengl.com/In-Practice/2D-Game/Render-text.

I will quickly describe my implementation and end by showing my output. When I create my TextRenderer object the following code is called:

TextRenderer::TextRenderer() :
    TextShader("text")//This creates text.glsl (shown later)
{
    glGenVertexArrays(1, &this->VAO);//.h file has GLuint members VAO and VBO
    glGenBuffers(1, &this->VBO);
    glBindVertexArray(this->VAO);
    glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

The text.glsl shader file created in the above constructor is as follows:

#include "common.glh"

varying vec2 texCoord0;

#if defined(VS_BUILD)
attribute vec4 vertex;

uniform mat4 T_MVP;

void main()
{
    gl_Position = T_MVP * vec4(vertex.xy, 0.0, 1.0);
    texCoord0 = vertex.zw;
} 

#elif defined(FS_BUILD)

uniform sampler2D H_text;
uniform vec3 H_textColor;

DeclareFragOutput(0, vec4);
void main()
{    
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(H_text, texCoord0).r);
    vec4 color = vec4(H_textColor, 1.0) * sampled;
    SetFragOutput(0, color);
}  
#endif

Following the creation of the TextRenderer object its Load function is called, the Load function uses a struct defined in the .h file which can be seen here along with the Load function:

/// Holds all state information relevant to a character as loaded using FreeType
struct Character {
    GLuint TextureID;   // ID handle of the glyph texture
    glm::ivec2 Size;    // Size of glyph
    glm::ivec2 Bearing; // Offset from baseline to left/top of glyph
    GLuint Advance;     // Horizontal offset to advance to next glyph
};
void TextRenderer::Load(std::string font, GLuint fontSize)
{
    // First clear the previously loaded Characters
    this->Characters.clear();
    // Then initialize and load the FreeType library
    FT_Library ft;
    if (FT_Init_FreeType(&ft)) // All functions return a value different than 0 whenever an error occurred
        printf("ERROR::FREETYPE: Could not init FreeType Library");
    // Load font as face
    FT_Face face;
    if (FT_New_Face(ft, "arial.ttf", 0, &face))
        printf("ERROR::FREETYPE: Failed to load font");
    // Set size to load glyphs as
    FT_Set_Pixel_Sizes(face, 0, fontSize);

    if (FT_Load_Char(face, 'X', FT_LOAD_RENDER))
        std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
    // Disable byte-alignment restriction
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    // Then for the first 128 ASCII characters, pre-load/compile their characters and store them
    for (GLubyte c = 0; c < 128; c++) 
    {
        // Load character glyph 
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            printf("ERROR::FREETYTPE: Failed to load Glyph");
            continue;
        }
        // Generate texture
        GLuint texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RED,
            face->glyph->bitmap.width,
            face->glyph->bitmap.rows,
            0,
            GL_RED,
            GL_UNSIGNED_BYTE,
            face->glyph->bitmap.buffer
        );
        // Set texture options
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // Now store character for later use
        Character character = {
            texture,
            glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
            glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
            face->glyph->advance.x
        };
        Characters.insert(std::pair<GLchar, Character>(c, character));
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    // Destroy FreeType once we're finished
    FT_Done_Face(face);
    FT_Done_FreeType(ft);
}

Follwoing this the TextRenderer object has its RenderTextRenderer function called every frame, this is as follows:

void TextRenderer::RenderTextRenderer(const RenderingEngine& renderingEngine) {
    //glEnable(GL_BLEND); //Commented for now
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //Commented for now
    glDisable(GL_DEPTH_TEST);
    this->TextShader.Bind();
    this->TextShader.UpdateUniformsTextRenderer(renderingEngine);
    RenderText("TEST testpqli", 20, 20, 1);//HAS THE DRAW CALL
    glEnable(GL_DEPTH_TEST);
    //glDisable(GL_BLEND); //Commented for now
}

The UpdateUniformsTextRenderer function and the RenderText function are both shown bellow respectively:

void Shader::UpdateUniformsTextRenderer(const RenderingEngine& renderingEngine)
{
    for (unsigned int i = 0; i < m_shaderData->GetUniformNames().size(); i++)
    {
        std::string uniformName = m_shaderData->GetUniformNames()[i];
        std::string uniformType = m_shaderData->GetUniformTypes()[i];

         if (uniformName.substr(0, 2) == "T_")
        {
             if (uniformName == "T_MVP")
                 SetUniformMatrix4f(uniformName, Matrix4f().InitOrthographic(0.0f, 800.0f, 0.0f, 600.0f, 0, 10.0f));//left, right, top, bottom, near, far
            else
                throw "Invalid Transform Uniform: " + uniformName;
        }
        else if (uniformName.substr(0, 2) == "H_") {
            if (uniformName == "H_text") {
                int samplerSlot = renderingEngine.GetSamplerSlot(uniformName);//set earlier using SetSamplerSlot("H_text", 4);
                SetUniformi(uniformName, samplerSlot);
            }
            else if (uniformName == "H_textColor")
                SetUniformVector3f(uniformName, Vector3f(0, 1, 0));//Green
            else
                throw "Invalid Transform Uniform: " + uniformName;
        }
    }
}
void TextRenderer::RenderText(std::string text, GLfloat x, GLfloat y, GLfloat scale)
{
    // Activate corresponding render state  
    this->TextShader.Bind();
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(this->VAO);

    // Iterate through all characters
    std::string::const_iterator c;
    for (c = text.begin(); c != text.end(); c++)
    {
        Character ch = Characters[*c];

        GLfloat xpos = x + ch.Bearing.x * scale;
        GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;

        GLfloat w = ch.Size.x * scale;
        GLfloat h = ch.Size.y * scale;
        // Update VBO for each character
        GLfloat vertices[6][4] = {
            { xpos,     ypos + h,   0.0, 1.0 },
            { xpos + w, ypos,       1.0, 0.0 },
            { xpos,     ypos,       0.0, 0.0 },

            { xpos,     ypos + h,   0.0, 1.0 },
            { xpos + w, ypos + h,   1.0, 1.0 },
            { xpos + w, ypos,       1.0, 0.0 }
        };
        // Render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.TextureID);
        // Update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // Render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // Now advance cursors for next glyph
        x += (ch.Advance >> 6) * scale; // Bitshift by 6 to get value in pixels (1/64th times 2^6 = 64)
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

All of this gives the following output (note I am rendering the string "TEST testpqli"):

enter image description here

Normally, my output seen in the above picture would be the result of something the linked too tutorial describes as "we can disable blending to see what the actual rendered quads look like", and as my blending code is commented out this all seems right on track, but If I un-comment my blending code in my RenderTextRenderer function shown earlier (just the glEnable(GL_BLEND); and glDisable(GL_BLEND); lines) then the output is the exact same, except the "text" becomes slightly more transparent when looking around the scene with things in the background like trees now been shown through the "text", and if I look at the sky the "text" loses its green color and become white as seen in the bellow image:

enter image description here

But if I then next un-comment specifically the line of code glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); the "text" completely disappears instead of solving the issue, as seen in the following image:

enter image description here

Therefore I have suspicions the issue lies with the glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); call or something to do with blending in general. Can anyone see where I have gone wrong? any advice is much appreciated.

\$\endgroup\$
17
  • \$\begingroup\$ When you get to render text, you load a single texture for the whole text and then, for each vertex you use only 0.0 and 1.0 uv coordinates. That cannot be right. You need, at the very least, different uv coordinates for each glyph quad. \$\endgroup\$
    – Jeffrey
    Mar 12 '20 at 19:53
  • \$\begingroup\$ @Jeffrey supports Monica, in the linked tutorial, the author are using multiple textures, one for each type of letter, which means the UV coordinates are correct in this case. It isn't the most optimized way of rendering text but as an exercise it's fine. \$\endgroup\$ Mar 12 '20 at 20:05
  • \$\begingroup\$ Ok good point. Then all I can see is blend modes and enabling texture. But, too much code to read... :-) \$\endgroup\$
    – Jeffrey
    Mar 12 '20 at 20:14
  • \$\begingroup\$ Since i am just tinkering with the same thing: it strongly looks like blending is not enabled in the renderloop, before the text render function is called (judging from the screenshot and a short view on the render function, but i haven't read it all). \$\endgroup\$
    – user136710
    Mar 12 '20 at 21:23
  • \$\begingroup\$ You must also set the blend function. And check function params: the internal format of glTexImage2D is the sized format, like GL_R8. But I am using opengl 4.5 ( "varying" and "attribute" are only known from historical reference to me) and after having played with the example (and the docu on the Freetype page) i now build a texture atlas and fill static and dynamic buffers for drawing. I can only say it works.Maybe start with the example right away and work from there on. \$\endgroup\$
    – user136710
    Mar 12 '20 at 22:42
1
\$\begingroup\$

In your text shader, you've forgot to set the sampler2D uniform's index. Each sampler in a shader need to be set to a specific index value, either at the creation of the shader (at the startup of the application) or each frame (not necessary, but possible). Whenever a sampler should be used in a shader, OpenGL needs to know what sampler to use, depending on which texture unit that has been set as the active (by calling glActiveTexture()).

Below I've added a working RenderTextRenderer function, with an added function call that sets the index of the texture sampler. It is set every frame but, like I wrote, it isn't necessary to do that. I do it only for demonstration purpose.

void TextRenderer::RenderTextRenderer(const Shader& shader, const RenderingEngine& renderingEngine, const Camera& camera)
{
    // Enable blending and set the blend function so that the text is rendered instead of the quads
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Disable the depth test since the text is being rendered in 2D
    glDisable(GL_DEPTH_TEST);

    this->TextShader.Bind();
    this->TextShader.UpdateUniformsTextRenderer(renderingEngine);

    /**
    * NEW CODE
    * Set the sampler named "H_text" to have index 0
    * When glActiveTexture(GL_TEXTURE0) is called, and a texture is later bound,
    * OpenGL is being told that the sampler named "H_text" should use that specific bound texture
    */
    glUniform1i(glGetUniformLocation(TextShader.GetShaderData()->GetProgram(), "H_text"), 0);

    RenderText("TEST testpqli", 20, 20, 1);

    // Enable the depth test again so that the 3D rendering will be correctly rendered in the next frame
    glEnable(GL_DEPTH_TEST);

    /**
    * Optionally disable the blending
    * Can be removed since the 3D world might have some parts that needs to be rendered (semi-)transparent
    */
    glDisable(GL_BLEND);
}

Result:

enter image description here

As you can see, the text is being rendered upside-down. To get the text to render correctly, you just need to edit the UV coordinates a bit, in the Rendertext function, at the point where the vbo is to be updated.

// Current UV setup
GLfloat vertices[6][4] =
{
    { xpos,     ypos + h,   0.0, 1.0 },
    { xpos + w, ypos,       1.0, 0.0 },
    { xpos,     ypos,       0.0, 0.0 },

    { xpos,     ypos + h,   0.0, 1.0 },
    { xpos + w, ypos + h,   1.0, 1.0 },
    { xpos + w, ypos,       1.0, 0.0 }
};

// Fixed UV setup, notice that the Y-element of the UV coordinates are flipped
GLfloat vertices[6][4] =
{
    { xpos,     ypos + h,   0.0, 0.0 },
    { xpos + w, ypos,       1.0, 1.0 },
    { xpos,     ypos,       0.0, 1.0 },

    { xpos,     ypos + h,   0.0, 0.0 },
    { xpos + w, ypos + h,   1.0, 0.0 },
    { xpos + w, ypos,       1.0, 1.0 }
};
\$\endgroup\$
4
  • \$\begingroup\$ I managed to flip the text the right way up by using the version of the RenderText function found here instead of here, but now I need to call "glDisable(GL_CULL_FACE);" before I call RenderText function as I no longer have the version of the code that "switched the order of the vertices a bit around to make sure all the vertices are still front facing". Is there a way I can flip the text the right way up without having to call "glDisable(GL_CULL_FACE);" as a result? \$\endgroup\$ Mar 17 '20 at 22:25
  • \$\begingroup\$ Yes. It is the winding order of the vertices. +1 @Daniel_1985 for your efforts :-) \$\endgroup\$
    – user136710
    Mar 17 '20 at 22:49
  • \$\begingroup\$ I edited my answer and added information about how to edit the UV to get the text to render correctly (without changing the culling setting). \$\endgroup\$ Mar 17 '20 at 22:55
  • \$\begingroup\$ Thanks for all the effort and time you put into answering my question Daniel, I truly appreciate it. \$\endgroup\$ Mar 18 '20 at 0:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .