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
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."