c ++ – FreeType Library – Multi-line text

I render text using the FreeType extended bookcase with OpenGL, but let's say I want to render a long line of text, as the following image shows:

enter description of image here

The text continues on the same line until it disappears from the screen. In the image above, you can see that the window may contain the word "Test" 11 times, but the 12 goes from the screen, so in a perfect world, I would just be able to put n (new line command in most systems) after the 11th word and the 12th would be rendered on a new line under the original. However, the command n doesn't work the same way FreeType as for std::cout and printf.

Therefore, my question is this – How could I render the text of a TextRenderer object on several lines, in the same way as n command works for single std::cout and printf calls? (I don't want to create a new object for each line of text, I can already do it this way but it seems to be wasting) Maybe there is a way to FreeType know that it is at the edge of a window and if so, render the following characters appropriately?

c ++ – Rendering text using the FreeType library does not work properly

Currently, I'm implementing text rendering in my game engine using the FreeType library by following the tutorial found here: https://learnopengl.com/In-Practice/2D-Game/Render-text. My current implementation is not working properly, you can see the result of my implementation in the following images (note that I make the text "Test test", and you can see 8 distinct cubic shapes one for each letter with a space in between ) words, the last 4 cubic forms being smaller and of different form compared to the capitalized version of the word, it therefore seems that it is at least close to rendering the string "Test test"):

enter description of image here

enter description of image here

First, there are obvious problems, to start with, you can see that the "text" is drawn from a projection perspective rather than orthographic, this is on purpose however because my system is already drawing with a perspective of projection and if the text was stuck flat on my screen in a 2D I do not see how the perspective in which it is drawn would change anything.

This leads to the following problem, the "text" is not pasted on my screen in a 2D way (like HUD elements in a game), it seems to float in 3D space, although if I look at the "text" exactly from the side, it will disappear, so it doesn't seem to have depth (z axis), only one position on the x and y axis. In addition, if I go beyond the right side and look from behind, the "text" disappears.

And finally the most obvious problem, the glyphs clearly are not rendered correctly because you cannot see the actual shape of the letters, but just the cubic space containing the letter.

My implementation is as follows:
((Warning: My engine is too big to explain every little thing that happens so this question shows a minimal code and only code relevant to this problem, described in a fairly high way)

In my engine, the scene is created using a scene graph of GameObjectis each with GameComponentthis is why i am creating a TextRendererObject and add a TextRenderer and add it to the scene in the following code:

Entity *textRendererObject = new Entity(...);
TextRenderer  *Text;
Text = new TextRenderer(50, 50);
Text->Load("font/arial.ttf", 240);
textRendererObject->AddComponent(Text);
AddToScene(textRendererObject);

the TextRenderer builder a struct that the .h defined file (used in load function) and load are respectively as follows:

TextRenderer::TextRenderer(GLuint width, GLuint height) :
    TextShader("text")//creates text shader (text.glsl)
{
   GLuint VAO, VBO;

    SetIsTextRenderer(true);

    glGenVertexArrays(1, &this->VAO);
    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);
}
/// 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
};
//...
std::map Characters;
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");//std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
    // Load font as face
    FT_Face face;
    if (FT_New_Face(ft, font.c_str(), 0, &face))
        printf("ERROR::FREETYPE: Failed to load font");//std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
    // Set size to load glyphs as
    FT_Set_Pixel_Sizes(face, 0, fontSize);
    // 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");//std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
            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(c, character));
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    // Destroy FreeType once we're finished
    FT_Done_Face(face);
    FT_Done_FreeType(ft);
}

The shader (text.glsl) created when the Textrenderer the object is created is as follows:

#include "common.glh"

varying vec2 texCoord0;
varying vec3 worldPos0;

#if defined(VS_BUILD)
attribute vec3 position;
attribute vec2 texCoord;

uniform mat4 T_model;
uniform mat4 T_MVP;

void main()
{
    gl_Position = T_MVP * vec4(position.xy, 0.0, 1.0);
    texCoord0 = texCoord;
    worldPos0 = (T_model * vec4(position.xy, 0.0, 1.0)).xyz;
} 

#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, texture2D(H_text, texCoord0).r);
    vec4 color = vec4(H_textColor, 1.0) * sampled;
    SetFragOutput(0, sampled * color);
}  
#endif

Following this implementation of the textRenderObject game object, sound Text game component and text.glsl shader, at each image, the following rendering function is called:

void TextRenderer::RenderTextRenderer(...) {
    this->TextShader.Bind();//"text.glsl" created earlier
    this->TextShader.UpdateUniformsTextRenderer(...);
    RenderText("TEST test", 100, 100, 1);//responsible for drawing 
}

UpdateUniformsTextRenderer(...) is responsible for defining values uniformpeach text.glsl and looks like this:

void Shader::UpdateUniformsTextRenderer(Transform* transform, const RenderingEngine& renderingEngine, const Camera& camera)
{
    Matrix4f worldMatrix = transform->GetTransformation();
    Matrix4f projectedMatrix = camera.GetViewProjection() * worldMatrix;
    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, projectedMatrix);
            else if (uniformName == "T_model")
                SetUniformMatrix4f(uniformName, worldMatrix);
            else
                throw "Invalid Transform Uniform: " + uniformName;
        }
        else if (uniformName.substr(0, 2) == "H_") {
            if (uniformName == "H_text") {//Texture used to draw text
                int samplerSlot = renderingEngine.GetSamplerSlot(uniformName);
                SetUniformi(uniformName, samplerSlot);
            }
            else if (uniformName == "H_textColor")
                SetUniformVector3f(uniformName, Vector3f(1, 0, 0));//red
        }
    }
}

And finally the function RenderText who actually draws the text is:

void TextRenderer::RenderText(std::string text, GLfloat x, GLfloat y, GLfloat scale)
{
    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 + (this->Characters('H').Bearing.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);
}

And this is my implementation, can anyone see where i was wrong? Any feedback is highly appreciated.