From 10138d5fd8f58181bd223659b29749d158c5337e Mon Sep 17 00:00:00 2001 From: karl Date: Tue, 4 May 2021 12:07:56 +0200 Subject: [PATCH] Add draft for text rendering --- SConstruct | 2 +- cpp/RenderBackend.cpp | 5 ++ cpp/TextLabel.cpp | 128 ++++++++++++++++++++++++++++++++++++ include/Gedeng/TextLabel.h | 37 +++++++++++ include/Gedeng/Vector3.h | 10 ++- test/parallax-demo/main.cpp | 7 ++ 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 cpp/TextLabel.cpp create mode 100644 include/Gedeng/TextLabel.h diff --git a/SConstruct b/SConstruct index cc7f2c8..8411d48 100644 --- a/SConstruct +++ b/SConstruct @@ -19,7 +19,7 @@ env = Environment(tools=['default', 'compilation_db']) env.CompilationDatabase() env.Append(CPPPATH=['cpp/', 'include/']) -env.Append(LIBS=['glfw', 'dl']) +env.Append(LIBS=['glfw', 'dl', 'freetype']) add_third_party_includes(env) add_strict_compile_flags(env) diff --git a/cpp/RenderBackend.cpp b/cpp/RenderBackend.cpp index 48bc929..450406e 100644 --- a/cpp/RenderBackend.cpp +++ b/cpp/RenderBackend.cpp @@ -15,6 +15,11 @@ void RenderBackend::initialize_window(unsigned int width, unsigned int height, S window = glfwCreateWindow(width, height, title, NULL, NULL); glfwMakeContextCurrent(window); gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } bool RenderBackend::is_window_created() { diff --git a/cpp/TextLabel.cpp b/cpp/TextLabel.cpp new file mode 100644 index 0000000..e07a305 --- /dev/null +++ b/cpp/TextLabel.cpp @@ -0,0 +1,128 @@ +// Must be the first include +#include + +// Other includes +#include +#include +#include + +#include "Gedeng/Logger.h" +#include "Gedeng/TextLabel.h" + +namespace Gedeng { + +TextLabel::TextLabel() : shader(Shader("Shader/text.vs", "Shader/text.fs")) { + FT_Library ft; + if (FT_Init_FreeType(&ft)) { + GG_CORE_ERROR("Couldn't create font library!"); + return; + } + + FT_Face face; + if (FT_New_Face(ft, "Resources/Fonts/Inter.ttf", 0, &face)) { + GG_CORE_ERROR("Couldn't load font from path!"); + return; + } + + // Set size + FT_Set_Pixel_Sizes(face, 0, 48); + + // Load first 128 ASCII Characters + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction + + for (unsigned char c = 0; c < 128; c++) { + // load character glyph + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { + GG_CORE_ERROR("Couldn't load character glyph!"); + continue; + } + // generate texture + unsigned int 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), + static_cast(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); + + // configure VAO/VBO for texture quads + // ----------------------------------- + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + valid = true; +} + +void TextLabel::render_text(String text, float x, float y, float scale, Vector3 color) { + // activate corresponding render state + shader.use(); + + glUniform3f(glGetUniformLocation(shader.ID, "textColor"), color.x, color.y, color.z); + + // FIXME: We need the screen size here... (or UI module size?) + glm::mat4 projection = glm::ortho(0.0f, static_cast(500), 0.0f, static_cast(500)); + glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + glActiveTexture(GL_TEXTURE0); + glBindVertexArray(vao); + + // iterate through all characters + for (size_t i = 0; i < text.get_length(); i++) { + Character ch = characters[text[i]]; + + float xpos = x + ch.bearing.x * scale; + float ypos = y - (ch.size.y - ch.bearing.y) * scale; + + float w = ch.size.x * scale; + float h = ch.size.y * scale; + // update VBO for each character + float vertices[6][4] = { + {xpos, ypos + h, 0.0f, 0.0f}, {xpos, ypos, 0.0f, 1.0f}, {xpos + w, ypos, 1.0f, 1.0f}, + + {xpos, ypos + h, 0.0f, 0.0f}, {xpos + w, ypos, 1.0f, 1.0f}, {xpos + w, ypos + h, 1.0f, 0.0f}}; + // render glyph texture over quad + glBindTexture(GL_TEXTURE_2D, ch.texture_id); + // update content of VBO memory + glBindBuffer(GL_ARRAY_BUFFER, 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 (note that advance is number of 1/64 pixels) + x += (ch.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th + // pixels by 64 to get amount of pixels)) + } + + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); +} + +bool TextLabel::is_valid() { + return valid; +} + +} // namespace Gedeng \ No newline at end of file diff --git a/include/Gedeng/TextLabel.h b/include/Gedeng/TextLabel.h new file mode 100644 index 0000000..99cd347 --- /dev/null +++ b/include/Gedeng/TextLabel.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include FT_FREETYPE_H + +#include "Gedeng/Shader.h" +#include "Gedeng/String.h" +#include "Gedeng/Vector3.h" + +namespace Gedeng { + +class TextLabel { + public: + TextLabel(); + + void render_text(String text, float x, float y, float scale, Vector3 color); + + bool is_valid(); + + private: + struct Character { + unsigned int texture_id; // ID handle of the glyph texture + glm::ivec2 size; // Size of glyph + glm::ivec2 bearing; // Offset from baseline to left/top of glyph + unsigned int advance; // Offset to advance to next glyph + }; + + std::map characters; + unsigned int vao, vbo; + bool valid; + + Shader shader; +}; + +} // namespace Gedeng \ No newline at end of file diff --git a/include/Gedeng/Vector3.h b/include/Gedeng/Vector3.h index 079c9ce..a6150bf 100644 --- a/include/Gedeng/Vector3.h +++ b/include/Gedeng/Vector3.h @@ -2,6 +2,14 @@ namespace Gedeng { -class Vector3 {}; +class Vector3 { + public: + Vector3(float x, float y, float z) : x(x), y(y), z(z) { + } + + float x; + float y; + float z; +}; } // namespace Gedeng \ No newline at end of file diff --git a/test/parallax-demo/main.cpp b/test/parallax-demo/main.cpp index b263e1a..64a6cc4 100644 --- a/test/parallax-demo/main.cpp +++ b/test/parallax-demo/main.cpp @@ -1,4 +1,6 @@ #include "Gedeng/Logger.h" +#include "Gedeng/TextLabel.h" +#include "Gedeng/Vector3.h" #define GEDENG_MAIN #include @@ -68,6 +70,9 @@ class ParallaxApp : public Gedeng::Application { // Quad which is rendered onto quad_mesh.rotate(delta * 25.0f, glm::normalize(glm::vec3(0.0, 0.0, 1.0))); quad_mesh.render(render_shader); + + debug_text.render_text(Gedeng::String("This is sample text"), 25.0f, 25.0f, 1.0f, + Gedeng::Vector3(1.0, 1.0, 0.0)); } private: @@ -86,6 +91,8 @@ class ParallaxApp : public Gedeng::Application { Gedeng::Texture normal; Gedeng::QuadMesh quad_mesh; + + Gedeng::TextLabel debug_text; }; Gedeng::Application *Gedeng::create_application() {