copenglgraphicsgame-engine

Rendering Text glitches and shows text quads behind and wrong color hex


I am trying to make a function in my game engine to draw text with stb_truetype , The problem is that when rendering it , for some reason there's black boxes behind the text and even when the color hex is white it is red for some reason , I have tried everything but can't get the color hex to fix or the black boxes to go away Here's the functions of my engine at the moment:

#include "../include/engine.h"
#include "../include/glad/glad.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "../include/GLFW/glfw3.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "../include/stb_truetype.h"

// External window
GLFWwindow* window;

// Global font variable for cleanup
Font globalFont; 

// 2D drawing state
bool is2DDrawingEnabled = false;

// Create a window
GLFWwindow* CreateWindow(const char* title, int width, int height) {
    if (!glfwInit()) {
        fprintf(stderr, "Failed to initialize GLFW\n");
        return NULL;
    }

    // Set OpenGL version to 2.1
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    
    window = glfwCreateWindow(width, height, title, NULL, NULL);
    if (!window) {
        fprintf(stderr, "Failed to create GLFW window\n");
        glfwTerminate();
        return NULL;
    }

    glfwMakeContextCurrent(window);
    gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); // Make sure glad supports OpenGL 2.1
    glViewport(0, 0, width, height);
    return window;
}

// Set background color using hex color codes like "#RRGGBB"
void SetBackColor(const char* hexColor) {
    if (hexColor[0] == '#') {
        unsigned int hexValue = (unsigned int)strtol(hexColor + 1, NULL, 16);
        float r = ((hexValue >> 16) & 0xFF) / 255.0f;
        float g = ((hexValue >> 8) & 0xFF) / 255.0f;
        float b = (hexValue & 0xFF) / 255.0f;
        glClearColor(r, g, b, 1.0f);
    } else {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }
}

// Load font function with error handling
Font LoadFont(const char* filename, int fontSize) {
    Font font = { 0 };

    // Load the font file
    FILE* fontFile = fopen(filename, "rb");
    if (!fontFile) {
        fprintf(stderr, "Failed to open font file: %s\n", filename);
        return font;
    }

    // Read font file into memory
    fseek(fontFile, 0, SEEK_END);
    long fileSize = ftell(fontFile);
    fseek(fontFile, 0, SEEK_SET);
    unsigned char* ttfBuffer = (unsigned char*)malloc(fileSize);
    if (fread(ttfBuffer, 1, fileSize, fontFile) != fileSize) {
        fprintf(stderr, "Failed to read font file: %s\n", filename);
        fclose(fontFile);
        free(ttfBuffer);
        return font;
    }
    fclose(fontFile);

    // Create bitmap for font
    font.bitmap = (unsigned char*)malloc(512 * 512);
    stbtt_bakedchar* bakedChars = (stbtt_bakedchar*)malloc(sizeof(stbtt_bakedchar) * 96);
    
    if (stbtt_BakeFontBitmap(ttfBuffer, 0, (float)fontSize, font.bitmap, 512, 512, 32, 96, bakedChars) <= 0) {
        fprintf(stderr, "Failed to bake font bitmap for: %s\n", filename);
        free(ttfBuffer);
        free(font.bitmap);
        free(bakedChars);
        return font;
    }

    // Generate texture
    glGenTextures(1, &font.textureID);
    glBindTexture(GL_TEXTURE_2D, font.textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 512, 512, 0, GL_RED, GL_UNSIGNED_BYTE, font.bitmap);

    // Set texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // Store baked chars in font structure
    font.glyphs = bakedChars; // Store the glyphs directly in the Font struct

    // Free temporary buffers
    free(ttfBuffer);
    
    font.baseSize = fontSize;
    
    // Store global font
    globalFont = font; 

    return font;
}

// Function to start 2D drawing
void Start2DDrawing() {
    if (!is2DDrawingEnabled) {
        glEnable(GL_TEXTURE_2D); // Enable 2D texturing
        glEnable(GL_BLEND); // Enable blending
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Set blend function
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        is2DDrawingEnabled = true;
    }
}


void End2DDrawing() {
    if (is2DDrawingEnabled) {
        glDisable(GL_TEXTURE_2D);
        is2DDrawingEnabled = false;
    }
}

// Function to draw text
void DrawText(Font font, const char* text, int posX, int posY, const char* hexColor) {
    glBindTexture(GL_TEXTURE_2D, font.textureID);
    
    // Set color based on hex value
    if (hexColor[0] == '#') {
        unsigned int hexValue = (unsigned int)strtol(hexColor + 1, NULL, 16);
        float r = ((hexValue >> 16) & 0xFF) / 255.0f;
        float g = ((hexValue >> 8) & 0xFF) / 255.0f;
        float b = (hexValue & 0xFF) / 255.0f;
        glColor4f(r, g, b, 1.0f); // Set color from hex
    } else {
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // Default to white if not a valid hex
    }

    float x = (float)posX;
    float y = (float)posY;

    for (const char *c = text; *c; c++) {
        if (*c < 32 || *c > 126) continue; // Skip unsupported characters
        stbtt_aligned_quad q;
        stbtt_GetBakedQuad(font.glyphs, 512, 512, *c - 32, &x, &y, &q, 1);
        
        // Draw the quad for the character
        glBegin(GL_QUADS);
            glTexCoord2f(q.s0, q.t0); glVertex2f(q.x0, q.y0);
            glTexCoord2f(q.s1, q.t0); glVertex2f(q.x1, q.y0);
            glTexCoord2f(q.s1, q.t1); glVertex2f(q.x1, q.y1);
            glTexCoord2f(q.s0, q.t1); glVertex2f(q.x0, q.y1);
        glEnd();
    }
}


bool WindowShouldEnd(GLFWwindow* window) {
    return glfwWindowShouldClose(window);
}

void HandleEvents(GLFWwindow* window) {
    glfwPollEvents();
    glfwSwapBuffers(window);
}

void ClearScreen() {
    glClear(GL_COLOR_BUFFER_BIT);
}

void error_callback(int error, const char* description) {
    fprintf(stderr, "Error: %s\n", description);
}

// Cleanup
void Cleanup() {
    // Clean up the global font
    glDeleteTextures(1, &globalFont.textureID);
    free(globalFont.bitmap);
    free(globalFont.glyphs); // Free baked glyphs
    glfwDestroyWindow(window);
    glfwTerminate();
}

The font things in engine.h:

// Color structure
typedef struct {
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
} Color;

// Font structure to hold font data
typedef struct Glyph {
    stbtt_bakedchar bakedChar; // Represents the baked character data from stb_truetype
    float advanceX;             // The amount to advance the cursor after rendering this glyph
    float offsetX;              // The offset for rendering this glyph
} Glyph;

typedef struct Font {
    GLuint textureID;           // Texture ID for the font atlas
    unsigned char *bitmap;      // Bitmap data for the font atlas
    int baseSize;               // Base font size (used for scaling)
    stbtt_bakedchar* glyphs;    // Pointer to an array of baked characters for the font
} Font;

this is the main app code and there is nothing wrong with it i guess:

#include "../include/engine.h"

int main() {
    window = CreateWindow("Test Window", 800, 600); // Initialize the window
    if (!window) return -1; // Exit if window creation failed

    globalFont = LoadFont("assets/AfacadFlux.ttf", 48); // Load the font
    if (globalFont.textureID == 0) return -1; // Exit if font loading failed

    while (!WindowShouldEnd(window)) {
        ClearScreen();
        SetBackColor("#222222");
        Start2DDrawing();

        Color white = {255, 255, 255, 255}; // White color
        DrawText(globalFont, "Hello, World!", 100, 100, "#FFFFFF"); // Draw text

        End2DDrawing();
        HandleEvents(window);
    }

    Cleanup(); // Clean up resources
    return 0;
}

This is the preview of the problem i am facing: Preview

What I tried to do:

Adding a function to render text in my engine

What I expected:

Transparent Text with working color hex


Solution

  • please read the glTexImage2D documentation.

    void glTexImage2D(  GLenum  target,
        GLint   level,
        GLint   internalFormat,
        GLsizei     width,
        GLsizei     height,
        GLint   border,
        GLenum  format,
        GLenum  type,
        const GLvoid *  data);
    

    The third argument is internalFormat and it defines "the number of color components in the texture. Must be 1, 2, 3, or 4, or one of the following symbolic constants...". GL_RED is not in the list. But still, assume you put 1(one channel used) into it.

    Next find is a format argument. Thats how OpenGL will treat your pixel data. It should be GL_ALPHA, GL_LUMINANCE, GL_LUMINANCE_ALPHA in your case. Because, as I understand it, your intention is to mask out the glyph based on its alpha value. That is why you are using blending.

    Why it might work as seen in your picture

    OpenGL 2.1 might supply alpha value 1 to your textured quad. That might be some sane default for blending operation that is used with a texture that has no alpha channel. I really can not find a reference to the default values used for the pixel transfer of OpenGL version 2.1. I guess it is your implementation of OpenGL that does 1 for the missing alpha.

    For the modern OpenGL the behaviour is stated in the reference. From OpenGL 4.6 Specification, section 8.25 (page 313 in the PDF):

    "When a texture lookup is performed on a texture with one or two components (e.g., GL_RED or GL_RG), the values of the missing components are substituted as follows:

    If the internal format is one-channel (e.g., GL_RED), then the green and blue channels are assigned the value 0.0, and the alpha channel is assigned the value 1.0."