single texture atlas for all font glyphs in TextRenderer
This commit is contained in:
parent
a59bcc0c4b
commit
4eda57b4e4
@ -26,8 +26,8 @@ namespace ZL
|
|||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
const char* CONST_ZIP_FILE = "resources.zip";
|
const char* CONST_ZIP_FILE = "resources.zip";
|
||||||
#else
|
#else
|
||||||
const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip";
|
// const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip";
|
||||||
//const char* CONST_ZIP_FILE = "";
|
const char* CONST_ZIP_FILE = "";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool g_exitBgAnimating = false;
|
static bool g_exitBgAnimating = false;
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
#include "render/OpenGlExtensions.h"
|
#include "render/OpenGlExtensions.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -15,7 +17,7 @@ TextRenderer::~TextRenderer()
|
|||||||
if (kv.second.texID) glDeleteTextures(1, &kv.second.texID);
|
if (kv.second.texID) glDeleteTextures(1, &kv.second.texID);
|
||||||
}*/
|
}*/
|
||||||
glyphs.clear();
|
glyphs.clear();
|
||||||
|
atlasTexture.reset();
|
||||||
textMesh.positionVBO.reset();
|
textMesh.positionVBO.reset();
|
||||||
/*
|
/*
|
||||||
if (vbo) glDeleteBuffers(1, &vbo);
|
if (vbo) glDeleteBuffers(1, &vbo);
|
||||||
@ -97,38 +99,188 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
}
|
}
|
||||||
|
|
||||||
FT_Set_Pixel_Sizes(face, 0, pixelSize);
|
FT_Set_Pixel_Sizes(face, 0, pixelSize);
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
glyphs.clear();
|
// glyphs.clear();
|
||||||
// Проходим по стандартным ASCII символам
|
|
||||||
|
// Сначала собираем все глифы в память
|
||||||
|
struct GlyphBitmap {
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
std::vector<char> data; // R8 байты (ширина*height)
|
||||||
|
Eigen::Vector2f bearing;
|
||||||
|
unsigned int advance = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::pair<char, GlyphBitmap>> glyphList;
|
||||||
|
glyphList.reserve(128 - 32);
|
||||||
|
|
||||||
|
int maxGlyphHeight = 0;
|
||||||
|
|
||||||
for (unsigned char c = 32; c < 128; ++c) {
|
for (unsigned char c = 32; c < 128; ++c) {
|
||||||
|
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||||
FT_Load_Char(face, c, FT_LOAD_RENDER);
|
// пропускаем если не удалось загрузить, но сохраняем пустой запись с advance
|
||||||
|
GlyphBitmap gb;
|
||||||
|
gb.width = 0;
|
||||||
|
gb.height = 0;
|
||||||
|
gb.bearing = { 0.f, 0.f };
|
||||||
|
gb.advance = 0;
|
||||||
|
glyphList.emplace_back((char)c, std::move(gb));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
TextureDataStruct glyphData;
|
GlyphBitmap gb;
|
||||||
glyphData.width = face->glyph->bitmap.width;
|
gb.width = face->glyph->bitmap.width;
|
||||||
glyphData.height = face->glyph->bitmap.rows;
|
gb.height = face->glyph->bitmap.rows;
|
||||||
glyphData.format = TextureDataStruct::R8;
|
gb.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top);
|
||||||
glyphData.mipmap = TextureDataStruct::NONE;
|
gb.advance = static_cast<unsigned int>(face->glyph->advance.x);
|
||||||
|
|
||||||
// Копируем буфер FreeType в вектор данных
|
size_t dataSize = static_cast<size_t>(gb.width) * static_cast<size_t>(gb.height);
|
||||||
size_t dataSize = glyphData.width * glyphData.height;
|
if (dataSize > 0) {
|
||||||
glyphData.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
|
gb.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
|
||||||
|
maxGlyphHeight = max(maxGlyphHeight, gb.height);
|
||||||
|
}
|
||||||
|
|
||||||
// Теперь создание текстуры — это одна строка!
|
glyphList.emplace_back((char)c, std::move(gb));
|
||||||
auto tex = std::make_shared<Texture>(glyphData);
|
|
||||||
|
|
||||||
GlyphInfo g;
|
|
||||||
g.texture = 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);
|
|
||||||
// Advance во FreeType измеряется в 1/64 пикселя
|
|
||||||
g.advance = (unsigned int)face->glyph->advance.x;
|
|
||||||
|
|
||||||
glyphs.emplace((char)c, g);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Пакуем глифы в один атлас (упрощённый алгоритм строковой укладки)
|
||||||
|
const int padding = 1;
|
||||||
|
const int maxAtlasWidth = 1024; // безопасное значение для большинства устройств
|
||||||
|
int curX = padding;
|
||||||
|
int curY = padding;
|
||||||
|
int rowHeight = 0;
|
||||||
|
int neededWidth = 0;
|
||||||
|
int neededHeight = 0;
|
||||||
|
|
||||||
|
// Предварительно вычислим требуемый размер, укладывая в maxAtlasWidth
|
||||||
|
for (auto& p : glyphList) {
|
||||||
|
const GlyphBitmap& gb = p.second;
|
||||||
|
int w = gb.width;
|
||||||
|
int h = gb.height;
|
||||||
|
if (curX + w + padding > maxAtlasWidth) {
|
||||||
|
// новая строка
|
||||||
|
neededWidth = max(neededWidth, curX);
|
||||||
|
curX = padding;
|
||||||
|
curY += rowHeight + padding;
|
||||||
|
rowHeight = 0;
|
||||||
|
}
|
||||||
|
curX += w + padding;
|
||||||
|
rowHeight = max(rowHeight, h);
|
||||||
|
}
|
||||||
|
neededWidth = max(neededWidth, curX);
|
||||||
|
neededHeight = curY + rowHeight + padding;
|
||||||
|
|
||||||
|
// Подгоняем к степеням двух (необязательно, но часто удобно)
|
||||||
|
auto nextPow2 = [](int v) {
|
||||||
|
int p = 1;
|
||||||
|
while (p < v) p <<= 1;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
atlasWidth = static_cast<size_t>(nextPow2(max(16, neededWidth)));
|
||||||
|
atlasHeight = static_cast<size_t>(nextPow2(max(16, neededHeight)));
|
||||||
|
|
||||||
|
// Ограничение - если получилось слишком большое, попробуем без power-of-two
|
||||||
|
if (atlasWidth > 4096) atlasWidth = static_cast<size_t>(neededWidth);
|
||||||
|
if (atlasHeight > 4096) atlasHeight = static_cast<size_t>(neededHeight);
|
||||||
|
|
||||||
|
// Создаём буфер атласа, инициализируем нулями (прозрачность)
|
||||||
|
std::vector<char> atlasData(atlasWidth * atlasHeight, 0);
|
||||||
|
|
||||||
|
// Второй проход - размещаем глифы и заполняем atlasData
|
||||||
|
curX = padding;
|
||||||
|
curY = padding;
|
||||||
|
rowHeight = 0;
|
||||||
|
|
||||||
|
for (auto &p : glyphList) {
|
||||||
|
char ch = p.first;
|
||||||
|
GlyphBitmap &gb = p.second;
|
||||||
|
|
||||||
|
if (gb.width == 0 || gb.height == 0) {
|
||||||
|
// пустой глиф — записываем UV с нулевым размером и метрики
|
||||||
|
GlyphInfo gi;
|
||||||
|
gi.size = Eigen::Vector2f(0.f, 0.f);
|
||||||
|
gi.bearing = gb.bearing;
|
||||||
|
gi.advance = gb.advance;
|
||||||
|
gi.uv = Eigen::Vector2f(0.f, 0.f);
|
||||||
|
gi.uvSize = Eigen::Vector2f(0.f, 0.f);
|
||||||
|
glyphs.emplace(ch, gi);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curX + gb.width + padding > static_cast<int>(atlasWidth)) {
|
||||||
|
// новая строка
|
||||||
|
curX = padding;
|
||||||
|
curY += rowHeight + padding;
|
||||||
|
rowHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Копируем строки глифа в atlasData
|
||||||
|
for (int row = 0; row < gb.height; ++row) {
|
||||||
|
// FreeType буфер, как мы ранее использовали, хранит строки подряд.
|
||||||
|
// Копируем gb.width байт из gb.data на позицию (curX, curY + row)
|
||||||
|
int destY = curY + row;
|
||||||
|
int destX = curX;
|
||||||
|
char* destPtr = atlasData.data() + destY * atlasWidth + destX;
|
||||||
|
const char* srcPtr = gb.data.data() + row * gb.width;
|
||||||
|
std::memcpy(destPtr, srcPtr, static_cast<size_t>(gb.width));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем информацию о глифе (в пикселях и UV)
|
||||||
|
GlyphInfo gi;
|
||||||
|
gi.size = Eigen::Vector2f((float)gb.width, (float)gb.height);
|
||||||
|
gi.bearing = gb.bearing;
|
||||||
|
gi.advance = gb.advance;
|
||||||
|
|
||||||
|
// UV: нормализуем относительно размера атласа. Здесь uv указывает на верх-лево.
|
||||||
|
gi.uv = Eigen::Vector2f((float)curX / (float)atlasWidth, (float)curY / (float)atlasHeight);
|
||||||
|
gi.uvSize = Eigen::Vector2f((float)gb.width / (float)atlasWidth, (float)gb.height / (float)atlasHeight);
|
||||||
|
|
||||||
|
glyphs.emplace(ch, gi);
|
||||||
|
|
||||||
|
curX += gb.width + padding;
|
||||||
|
rowHeight = max(rowHeight, gb.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Проходим по стандартным ASCII символам
|
||||||
|
// for (unsigned char c = 32; c < 128; ++c) {
|
||||||
|
//
|
||||||
|
// FT_Load_Char(face, c, FT_LOAD_RENDER);
|
||||||
|
|
||||||
|
// TextureDataStruct glyphData;
|
||||||
|
// glyphData.width = face->glyph->bitmap.width;
|
||||||
|
// glyphData.height = face->glyph->bitmap.rows;
|
||||||
|
// glyphData.format = TextureDataStruct::R8;
|
||||||
|
// glyphData.mipmap = TextureDataStruct::NONE;
|
||||||
|
|
||||||
|
// // Копируем буфер FreeType в вектор данных
|
||||||
|
// size_t dataSize = glyphData.width * glyphData.height;
|
||||||
|
// glyphData.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
|
||||||
|
|
||||||
|
// // Теперь создание текстуры — это одна строка!
|
||||||
|
// auto tex = std::make_shared<Texture>(glyphData);
|
||||||
|
|
||||||
|
//GlyphInfo g;
|
||||||
|
// g.texture = 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);
|
||||||
|
// // Advance во FreeType измеряется в 1/64 пикселя
|
||||||
|
// g.advance = (unsigned int)face->glyph->advance.x;
|
||||||
|
|
||||||
|
// glyphs.emplace((char)c, g);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Создаём Texture из atlasData (R8)
|
||||||
|
TextureDataStruct atlasTex;
|
||||||
|
atlasTex.width = atlasWidth;
|
||||||
|
atlasTex.height = atlasHeight;
|
||||||
|
atlasTex.format = TextureDataStruct::R8;
|
||||||
|
atlasTex.mipmap = TextureDataStruct::NONE;
|
||||||
|
atlasTex.data = std::move(atlasData);
|
||||||
|
|
||||||
|
atlasTexture = std::make_shared<Texture>(atlasTex);
|
||||||
|
|
||||||
// Очистка
|
// Очистка
|
||||||
FT_Done_Face(face);
|
FT_Done_Face(face);
|
||||||
FT_Done_FreeType(ft);
|
FT_Done_FreeType(ft);
|
||||||
@ -142,7 +294,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
|
|
||||||
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color)
|
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color)
|
||||||
{
|
{
|
||||||
if (!r || text.empty()) return;
|
if (!r || text.empty() || !atlasTexture) return;
|
||||||
|
|
||||||
// 1. Считаем ширину для центрирования
|
// 1. Считаем ширину для центрирования
|
||||||
float totalW = 0.0f;
|
float totalW = 0.0f;
|
||||||
@ -180,13 +332,27 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
|||||||
textData.PositionData.push_back({ xpos + w, ypos, 0.0f });
|
textData.PositionData.push_back({ xpos + w, ypos, 0.0f });
|
||||||
textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f });
|
textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f });
|
||||||
|
|
||||||
// UV-координаты (здесь есть нюанс с атласом, ниже поясню)
|
// TexCoords — на основе UV позиции и размера в атласе (uv указывает на верх-лево)
|
||||||
textData.TexCoordData.push_back({ 0.0f, 0.0f });
|
float u0 = g.uv.x();
|
||||||
textData.TexCoordData.push_back({ 0.0f, 1.0f });
|
float v0 = g.uv.y();
|
||||||
textData.TexCoordData.push_back({ 1.0f, 1.0f });
|
float u1 = u0 + g.uvSize.x();
|
||||||
textData.TexCoordData.push_back({ 0.0f, 0.0f });
|
float v1 = v0 + g.uvSize.y();
|
||||||
textData.TexCoordData.push_back({ 1.0f, 1.0f });
|
|
||||||
textData.TexCoordData.push_back({ 1.0f, 0.0f });
|
//// UV-координаты (здесь есть нюанс с атласом, ниже поясню)
|
||||||
|
//textData.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||||
|
//textData.TexCoordData.push_back({ 0.0f, 1.0f });
|
||||||
|
//textData.TexCoordData.push_back({ 1.0f, 1.0f });
|
||||||
|
//textData.TexCoordData.push_back({ 0.0f, 0.0f });
|
||||||
|
//textData.TexCoordData.push_back({ 1.0f, 1.0f });
|
||||||
|
//textData.TexCoordData.push_back({ 1.0f, 0.0f });
|
||||||
|
|
||||||
|
// Соответствие прежней системе UV: (0,0)=верх-лево, (0,1)=ниж-лево
|
||||||
|
textData.TexCoordData.push_back({ u0, v0 });
|
||||||
|
textData.TexCoordData.push_back({ u0, v1 });
|
||||||
|
textData.TexCoordData.push_back({ u1, v1 });
|
||||||
|
textData.TexCoordData.push_back({ u0, v0 });
|
||||||
|
textData.TexCoordData.push_back({ u1, v1 });
|
||||||
|
textData.TexCoordData.push_back({ u1, v0 });
|
||||||
|
|
||||||
penX += (g.advance >> 6) * scale;
|
penX += (g.advance >> 6) * scale;
|
||||||
}
|
}
|
||||||
@ -214,33 +380,35 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
|||||||
r->RenderUniform4fv("uColor", color.data());
|
r->RenderUniform4fv("uColor", color.data());
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, atlasTexture->getTexID());
|
||||||
|
|
||||||
// ВНИМАНИЕ: Так как у тебя каждый символ — это отдельная текстура,
|
|
||||||
// нам всё равно придется делать glDrawArrays в цикле, ЛИБО использовать атлас.
|
|
||||||
// Если оставляем текущую систему с разными текстурами:
|
|
||||||
|
|
||||||
r->EnableVertexAttribArray("vPosition");
|
r->EnableVertexAttribArray("vPosition");
|
||||||
r->EnableVertexAttribArray("vTexCoord");
|
r->EnableVertexAttribArray("vTexCoord");
|
||||||
|
|
||||||
for (size_t i = 0; i < text.length(); ++i) {
|
//for (size_t i = 0; i < text.length(); ++i) {
|
||||||
auto it = glyphs.find(text[i]);
|
// auto it = glyphs.find(text[i]);
|
||||||
if (it == glyphs.end()) continue;
|
// if (it == glyphs.end()) continue;
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, it->second.texture->getTexID());
|
// glBindTexture(GL_TEXTURE_2D, it->second.texture->getTexID());
|
||||||
|
|
||||||
// Отрисовываем по 6 вершин за раз
|
// // Отрисовываем по 6 вершин за раз
|
||||||
// Нам нужно вручную биндить VBO, так как DrawVertexRenderStruct рисует всё сразу
|
// // Нам нужно вручную биндить VBO, так как DrawVertexRenderStruct рисует всё сразу
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, textMesh.positionVBO->getBuffer());
|
// glBindBuffer(GL_ARRAY_BUFFER, textMesh.positionVBO->getBuffer());
|
||||||
r->VertexAttribPointer3fv("vPosition", 0, (const char*)(i * 6 * sizeof(Vector3f)));
|
// r->VertexAttribPointer3fv("vPosition", 0, (const char*)(i * 6 * sizeof(Vector3f)));
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, textMesh.texCoordVBO->getBuffer());
|
// glBindBuffer(GL_ARRAY_BUFFER, textMesh.texCoordVBO->getBuffer());
|
||||||
r->VertexAttribPointer2fv("vTexCoord", 0, (const char*)(i * 6 * sizeof(Vector2f)));
|
// r->VertexAttribPointer2fv("vTexCoord", 0, (const char*)(i * 6 * sizeof(Vector2f)));
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
// glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
}
|
//}
|
||||||
|
r->DrawVertexRenderStruct(textMesh);
|
||||||
|
|
||||||
r->DisableVertexAttribArray("vPosition");
|
r->DisableVertexAttribArray("vPosition");
|
||||||
r->DisableVertexAttribArray("vTexCoord");
|
r->DisableVertexAttribArray("vTexCoord");
|
||||||
r->shaderManager.PopShader();
|
r->shaderManager.PopShader();
|
||||||
|
|
||||||
|
// Сброс бинда текстуры не обязателен, но можно для чистоты
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
@ -12,7 +12,10 @@
|
|||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
struct GlyphInfo {
|
struct GlyphInfo {
|
||||||
std::shared_ptr<Texture> texture; // Texture for glyph
|
// std::shared_ptr<Texture> texture; // Texture for glyph
|
||||||
|
Eigen::Vector2f uv; // u,v координата левого верхнего угла в атласе (0..1)
|
||||||
|
Eigen::Vector2f uvSize; // ширина/высота в UV (0..1)
|
||||||
|
|
||||||
Eigen::Vector2f size; // glyph size in pixels
|
Eigen::Vector2f size; // glyph size in pixels
|
||||||
Eigen::Vector2f bearing; // offset from baseline
|
Eigen::Vector2f bearing; // offset from baseline
|
||||||
unsigned int advance = 0; // advance.x in 1/64 pixels
|
unsigned int advance = 0; // advance.x in 1/64 pixels
|
||||||
@ -37,6 +40,11 @@ private:
|
|||||||
//unsigned int vao = 0;
|
//unsigned int vao = 0;
|
||||||
//unsigned int vbo = 0;
|
//unsigned int vbo = 0;
|
||||||
|
|
||||||
|
// единый атлас для всех глифов
|
||||||
|
std::shared_ptr<Texture> atlasTexture;
|
||||||
|
size_t atlasWidth = 0;
|
||||||
|
size_t atlasHeight = 0;
|
||||||
|
|
||||||
VertexRenderStruct textMesh;
|
VertexRenderStruct textMesh;
|
||||||
|
|
||||||
std::string shaderName = "text2d";
|
std::string shaderName = "text2d";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user