add freetype-based text rendering and box labels
This commit is contained in:
1 parent
96d879076a
commit
f41228acf8
9 files changed
+406
-9
No files matched your search
@@ -0,0 +1,205 @@
|
||||
#include "render/TextRenderer.h"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "Environment.h"
|
||||
#include "render/OpenGlExtensions.h" // если у тебя там gl* указатели
|
||||
#include <iostream>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
TextRenderer::~TextRenderer()
|
||||
{
|
||||
for (auto& kv : glyphs) {
|
||||
if (kv.second.texID) glDeleteTextures(1, &kv.second.texID);
|
||||
}
|
||||
glyphs.clear();
|
||||
|
||||
if (vbo) glDeleteBuffers(1, &vbo);
|
||||
// if (vao) glDeleteVertexArrays(1, &vao);
|
||||
vao = 0;
|
||||
vbo = 0;
|
||||
}
|
||||
|
||||
bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixelSize)
|
||||
{
|
||||
r = &renderer;
|
||||
|
||||
// Добавим шейдер (если у тебя shaderManager работает так же, как с default)
|
||||
// Подстрой под твой AddShaderFromFiles: тут без zip.
|
||||
r->shaderManager.AddShaderFromFiles(shaderName,
|
||||
"resources/shaders/text2d.vertex",
|
||||
"resources/shaders/text2d.fragment",
|
||||
"" // ZIP пустой
|
||||
);
|
||||
|
||||
if (!loadGlyphs(ttfPath, pixelSize)) return false;
|
||||
|
||||
// VAO/VBO для 6 вершин (2 треугольника) * (pos.xy + uv.xy) = 4 float
|
||||
// glGenVertexArrays(1, &vao);
|
||||
glGenBuffers(1, &vbo);
|
||||
|
||||
//glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize)
|
||||
{
|
||||
FT_Library ft;
|
||||
if (FT_Init_FreeType(&ft)) {
|
||||
std::cerr << "FreeType: FT_Init_FreeType failed\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
if (FT_New_Face(ft, ttfPath.c_str(), 0, &face)) {
|
||||
std::cerr << "FreeType: failed to load font: " << ttfPath << "\n";
|
||||
FT_Done_FreeType(ft);
|
||||
return false;
|
||||
}
|
||||
|
||||
FT_Set_Pixel_Sizes(face, 0, pixelSize);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
glyphs.clear();
|
||||
for (unsigned char c = 32; c < 128; ++c) {
|
||||
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int tex;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
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);
|
||||
|
||||
GlyphInfo g;
|
||||
g.texID = tex;
|
||||
g.size = Eigen::Vector2f((float)face->glyph->bitmap.width, (float)face->glyph->bitmap.rows);
|
||||
g.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top);
|
||||
g.advance = (unsigned int)face->glyph->advance.x;
|
||||
|
||||
glyphs.emplace((char)c, g);
|
||||
}
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered)
|
||||
{
|
||||
if (!r) return;
|
||||
|
||||
// Считаем ширину строки для центрирования
|
||||
float totalW = 0.0f;
|
||||
if (centered) {
|
||||
for (char ch : text) {
|
||||
auto it = glyphs.find(ch);
|
||||
if (it == glyphs.end()) continue;
|
||||
totalW += (it->second.advance >> 6) * scale;
|
||||
}
|
||||
x -= totalW * 0.5f;
|
||||
}
|
||||
|
||||
r->shaderManager.PushShader(shaderName);
|
||||
|
||||
// Орто-проекция в пикселях: (0..W, 0..H)
|
||||
// ВАЖНО: мы рисуем в координатах Game::drawBoxesLabels() как uiX/uiY
|
||||
// Здесь предположим, что (0,0) внизу слева.
|
||||
float W = (float)Environment::width;
|
||||
float H = (float)Environment::height;
|
||||
|
||||
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
|
||||
proj(0,0) = 2.0f / W;
|
||||
proj(1,1) = 2.0f / H;
|
||||
proj(0,3) = -1.0f;
|
||||
proj(1,3) = -1.0f;
|
||||
|
||||
// uProjection
|
||||
r->RenderUniformMatrix4fv("uProjection", false, proj.data());
|
||||
r->RenderUniform1i("uText", 0);
|
||||
|
||||
// белый цвет
|
||||
float color[3] = {1.0f, 1.0f, 1.0f};
|
||||
r->RenderUniform3fv("uColor", color);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
// glBindVertexArray(vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
r->EnableVertexAttribArray("vPosition");
|
||||
r->EnableVertexAttribArray("vTexCoord");
|
||||
|
||||
r->VertexAttribPointer2fv("vPosition", 4 * sizeof(float), (const char*)0);
|
||||
r->VertexAttribPointer2fv("vTexCoord", 4 * sizeof(float), (const char*)(2 * sizeof(float)));
|
||||
|
||||
float penX = x;
|
||||
float penY = y;
|
||||
|
||||
for (char ch : text) {
|
||||
auto it = glyphs.find(ch);
|
||||
if (it == glyphs.end()) continue;
|
||||
|
||||
const GlyphInfo& g = it->second;
|
||||
|
||||
float xpos = penX + g.bearing.x() * scale;
|
||||
float ypos = penY - (g.size.y() - g.bearing.y()) * scale;
|
||||
|
||||
float w = g.size.x() * scale;
|
||||
float h = g.size.y() * scale;
|
||||
|
||||
// 2 треугольника
|
||||
float verts[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 }
|
||||
};
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, g.texID);
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
penX += (g.advance >> 6) * scale;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
r->DisableVertexAttribArray("vPosition");
|
||||
r->DisableVertexAttribArray("vTexCoord");
|
||||
|
||||
r->shaderManager.PopShader();
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <Eigen/Dense>
|
||||
#include "render/Renderer.h"
|
||||
|
||||
|
||||
namespace ZL {
|
||||
|
||||
struct GlyphInfo {
|
||||
unsigned int texID = 0; // GL texture for glyph
|
||||
Eigen::Vector2f size; // glyph size in pixels
|
||||
Eigen::Vector2f bearing; // offset from baseline
|
||||
unsigned int advance = 0; // advance.x in 1/64 pixels
|
||||
};
|
||||
|
||||
class TextRenderer {
|
||||
public:
|
||||
TextRenderer() = default;
|
||||
~TextRenderer();
|
||||
|
||||
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize);
|
||||
void drawText(const std::string& text, float x, float y, float scale, bool centered);
|
||||
|
||||
private:
|
||||
bool loadGlyphs(const std::string& ttfPath, int pixelSize);
|
||||
|
||||
Renderer* r = nullptr;
|
||||
|
||||
std::unordered_map<char, GlyphInfo> glyphs;
|
||||
|
||||
// OpenGL objects for a dynamic quad
|
||||
unsigned int vao = 0;
|
||||
unsigned int vbo = 0;
|
||||
|
||||
std::string shaderName = "text2d";
|
||||
};
|
||||
|
||||
} // namespace ZL
|
||||
Reference in new issue
Block a user