From 1378b17ef6106876c93410705c884ae5041d612c Mon Sep 17 00:00:00 2001 From: Guillaume Sarthou Date: Fri, 16 Aug 2024 16:58:42 +0200 Subject: [PATCH] [TextRenderer] render text at 3d pose --- CMakeLists.txt | 5 +- .../Engine/Graphics/OpenGL/Renderer.h | 2 + .../Engine/Graphics/OpenGL/TextRenderer.h | 40 +++++ src/Engine/Graphics/OpenGL/Renderer.cpp | 21 +++ src/Engine/Graphics/OpenGL/TextRenderer.cpp | 138 ++++++++++++++++++ 5 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 include/overworld/Engine/Graphics/OpenGL/TextRenderer.h create mode 100644 src/Engine/Graphics/OpenGL/TextRenderer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d58787dc..1d0e1beb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ find_package(assimp REQUIRED) find_package(Bullet REQUIRED) find_package(Eigen3 3.3 REQUIRED NO_MODULE) find_package(glm CONFIG REQUIRED) +find_package(Freetype REQUIRED) include(FetchContent) include(CheckLanguage) @@ -171,9 +172,11 @@ owds_add_library(overworld_graphics src/Engine/Graphics/OpenGL/Renderer.cpp src/Engine/Graphics/OpenGL/Screen.cpp src/Engine/Graphics/OpenGL/Shader.cpp + src/Engine/Graphics/OpenGL/TextRenderer.cpp src/Engine/Graphics/OpenGL/AmbientShadow.cpp src/Engine/Graphics/OpenGL/Texture2D.cpp) -target_link_libraries(overworld_graphics PUBLIC overworld_engine_common overworld_glad assimp glfw) +target_link_libraries(overworld_graphics PUBLIC overworld_engine_common overworld_glad assimp glfw ${FREETYPE_LIBRARIES}) +target_include_directories(overworld_graphics PRIVATE ${FREETYPE_INCLUDE_DIRS}) target_compile_options(overworld_graphics PRIVATE $<$:-Wno-unused-function>) # ################################## diff --git a/include/overworld/Engine/Graphics/OpenGL/Renderer.h b/include/overworld/Engine/Graphics/OpenGL/Renderer.h index 587096be..2378cffa 100644 --- a/include/overworld/Engine/Graphics/OpenGL/Renderer.h +++ b/include/overworld/Engine/Graphics/OpenGL/Renderer.h @@ -18,6 +18,7 @@ #include "overworld/Engine/Graphics/OpenGL/PointShadow.h" #include "overworld/Engine/Graphics/OpenGL/Screen.h" #include "overworld/Engine/Graphics/OpenGL/Shader.h" +#include "overworld/Engine/Graphics/OpenGL/TextRenderer.h" namespace owds { class Window; @@ -46,6 +47,7 @@ namespace owds { Cubemap sky_; AmbientShadow shadow_; PointShadow point_shadows_; + TextRenderer text_renderer_; float max_fps_ = 120; float delta_time_; float last_frame_; diff --git a/include/overworld/Engine/Graphics/OpenGL/TextRenderer.h b/include/overworld/Engine/Graphics/OpenGL/TextRenderer.h new file mode 100644 index 00000000..df538d0f --- /dev/null +++ b/include/overworld/Engine/Graphics/OpenGL/TextRenderer.h @@ -0,0 +1,40 @@ +#ifndef OWDS_TEXTRENDERER_H +#define OWDS_TEXTRENDERER_H + +#include +#include +#include +#include + +namespace owds { + + class Shader; + + struct Character_t + { + unsigned int texture_id_; + glm::ivec2 size; // size of glyph + glm::ivec2 bearing; // offset from baseline to left/top of glyph + unsigned int advance; // horizontal offset to advance to next glyph + }; + + class TextRenderer + { + public: + void init(); + + bool load(const std::string& font, unsigned int font_size); + + void renderText(Shader& shader, const std::string& text, const glm::vec3& position, float height, const glm::vec3& color); + + private: + unsigned int vao_; + unsigned int vbo_; + unsigned int pixel_size_; + + std::map characters_; + }; + +} // namespace owds + +#endif // OWDS_TEXTRENDERER_H \ No newline at end of file diff --git a/src/Engine/Graphics/OpenGL/Renderer.cpp b/src/Engine/Graphics/OpenGL/Renderer.cpp index 10aa299f..a8006115 100644 --- a/src/Engine/Graphics/OpenGL/Renderer.cpp +++ b/src/Engine/Graphics/OpenGL/Renderer.cpp @@ -94,10 +94,15 @@ namespace owds { shaders_.insert({ "depthcube", {"depthcube_shader.vs", "depthcube_shader.fs", "depthcube_shader.gs"} }); + shaders_.insert({ + "text", {"text_shader.vs", "text_shader.fs"} + }); sky_.init("/home/gsarthou/Robots/Dacobot2/ros2_ws/src/overworld/models/textures/skybox/Footballfield/"); shadow_.init(render_camera_.getNearPlane(), render_camera_.getFarPlane()); + text_renderer_.init(); + text_renderer_.load("/usr/share/fonts/truetype/open-sans/OpenSans-Regular.ttf", 48); shaders_.at("screen").use(); shaders_.at("screen").setInt("screenTexture", 0); @@ -319,6 +324,7 @@ namespace owds { { glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glDisable(GL_BLEND); // glStencilFunc(GL_ALWAYS, 1, 0xFF); // all fragments should pass the stencil test // glStencilMask(0xFF); // enable writing to the stencil buffer @@ -329,6 +335,7 @@ namespace owds { auto& sky_shader = shaders_.at("sky"); auto& shadow_shader = shaders_.at("depth"); auto& shadow_point_shader = shaders_.at("depthcube"); + auto& text_shader = shaders_.at("text"); // 0. draw scene as normal in depth buffers @@ -389,6 +396,8 @@ namespace owds { renderModels(light_shader, 2); + // 1.2 draw background + sky_shader.use(); glm::mat4 view = glm::mat4(glm::mat3(render_camera_.getViewMatrix())); sky_shader.setMat4("view", view); @@ -396,7 +405,19 @@ namespace owds { sky_.draw(sky_shader); + // 1.3 draw text + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + text_shader.use(); + text_shader.setMat4("projection", render_camera_.getProjectionMatrix()); + text_shader.setMat4("view", render_camera_.getViewMatrix()); + + text_renderer_.renderText(text_shader, "tesH", glm::vec3(0, 0, 5), 1, glm::vec3(1.f, 0.f, 1.f)); + // 2. now blit multisampled buffer(s) to normal colorbuffer of intermediate FBO. Image is stored in screenTexture + glDisable(GL_BLEND); screen_.generateColorTexture(); // 3. now render quad with scene's visuals as its texture image diff --git a/src/Engine/Graphics/OpenGL/TextRenderer.cpp b/src/Engine/Graphics/OpenGL/TextRenderer.cpp new file mode 100644 index 00000000..90b7ef35 --- /dev/null +++ b/src/Engine/Graphics/OpenGL/TextRenderer.cpp @@ -0,0 +1,138 @@ +#include "overworld/Engine/Graphics/OpenGL/TextRenderer.h" + +#include +#include + +#include "glad/glad.h" +#include "overworld/Engine/Graphics/OpenGL/Shader.h" +#include "overworld/Utility/ShellDisplay.h" + +namespace owds { + + void TextRenderer::init() + { + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + glBindVertexArray(vao_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 5, nullptr, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + bool TextRenderer::load(const std::string& font, unsigned int font_size) + { + characters_.clear(); + + FT_Library ft; + if(FT_Init_FreeType(&ft) != 0) // all functions return a value different than 0 whenever an error occurred + { + ShellDisplay::error("[TextRenderer] Could not init FreeType Library"); + return false; + } + + FT_Face face; + if(FT_New_Face(ft, font.c_str(), 0, &face)) + { + ShellDisplay::error("[TextRenderer] CFailed to load font " + font); + return false; + } + + FT_Set_Pixel_Sizes(face, 0, font_size); + pixel_size_ = font_size; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + for(GLubyte c = 0; c < 128; c++) + { + if(FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + ShellDisplay::error("[TextRenderer] Failed to load Glyph " + std::string(1, c)); + continue; + } + + unsigned int texture = 0; + 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); + + 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); + + Character_t character = { + texture, + glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), + glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), + (unsigned int)face->glyph->advance.x}; + characters_.emplace((char)c, character); + } + glBindTexture(GL_TEXTURE_2D, 0); + + FT_Done_Face(face); + FT_Done_FreeType(ft); + + return true; + } + + void TextRenderer::renderText(Shader& shader, const std::string& text, const glm::vec3& position, float height, const glm::vec3& color) + { + shader.use(); + shader.setVec3("text_color", color); + + glActiveTexture(GL_TEXTURE0); + glBindVertexArray(vao_); + + height = height / (float)pixel_size_; + + float x = position.x; + float y = position.y; + float z = position.z; + + std::string::const_iterator c; + for(c = text.begin(); c != text.end(); c++) + { + Character_t& ch = characters_[*c]; + + float xpos = x + ch.bearing.x * height; + float zpos = z - (ch.size.y - ch.bearing.y) * height; + + float w = ch.size.x * height; + float h = ch.size.y * height; + + float vertices[6 * 5] = { + xpos, y, zpos + h, 0.0f, 0.0f, + xpos, y, zpos, 0.0f, 1.0f, + xpos + w, y, zpos, 1.0f, 1.0f, + + xpos, y, zpos + h, 0.0f, 0.0f, + xpos + w, y, zpos, 1.0f, 1.0f, + xpos + w, y, zpos + h, 1.0f, 0.0f}; + + glBindTexture(GL_TEXTURE_2D, ch.texture_id_); + 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); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + x += (ch.advance >> 6) * height; // bitshift by 6 to get value in pixels (1/64th times 2^6 = 64) + } + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + } + +} // namespace owds \ No newline at end of file