Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL font rendering using Freetype2

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.

like image 363
Bentebent Avatar asked Nov 28 '13 16:11

Bentebent


People also ask

How are fonts rendered?

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.

What is text rendering?

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.


1 Answers

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.

like image 165
Bentebent Avatar answered Oct 22 '22 12:10

Bentebent