feat(渲染): 优化字体图集和精灵批处理系统
- 在GLFontAtlas中重构字体图集实现,使用stb_rect_pack进行动态矩形打包 - 增加图集尺寸至1024x1024并添加字形间距 - 改进字形UV计算和纹理坐标处理 - 在GLSpriteBatch中保存viewProjection矩阵并优化着色器uniform设置 - 修正文本渲染中的字形位置计算和精灵中心点处理 - 优化纹理坐标生成逻辑以正确处理UV翻转
This commit is contained in:
parent
30b677f192
commit
6b4ce69657
|
|
@ -13,6 +13,7 @@ namespace extra2d {
|
|||
|
||||
// ============================================================================
|
||||
// OpenGL 字体图集实现 (使用 STB 库)
|
||||
// 使用 stb_rect_pack 进行动态矩形打包,支持动态缓存字形
|
||||
// ============================================================================
|
||||
class GLFontAtlas : public FontAtlas {
|
||||
public:
|
||||
|
|
@ -31,6 +32,7 @@ public:
|
|||
Vec2 measureText(const std::string& text) override;
|
||||
|
||||
private:
|
||||
// 字形数据内部结构
|
||||
struct GlyphData {
|
||||
float width;
|
||||
float height;
|
||||
|
|
@ -40,17 +42,16 @@ private:
|
|||
float u0, v0, u1, v1;
|
||||
};
|
||||
|
||||
struct PackedCharData {
|
||||
stbtt_packedchar packedChar;
|
||||
bool valid;
|
||||
};
|
||||
// 图集配置 - 增大尺寸以支持更多字符
|
||||
static constexpr int ATLAS_WIDTH = 1024;
|
||||
static constexpr int ATLAS_HEIGHT = 1024;
|
||||
static constexpr int PADDING = 2; // 字形之间的间距
|
||||
|
||||
bool useSDF_;
|
||||
int fontSize_;
|
||||
|
||||
Ptr<GLTexture> texture_;
|
||||
std::unordered_map<char32_t, GlyphData> glyphs_;
|
||||
std::unordered_map<char32_t, PackedCharData> packedChars_;
|
||||
float lineHeight_;
|
||||
float ascent_;
|
||||
float descent_;
|
||||
|
|
@ -61,15 +62,21 @@ private:
|
|||
stbtt_fontinfo fontInfo_;
|
||||
float scale_;
|
||||
|
||||
// 图集打包参数
|
||||
static constexpr int ATLAS_WIDTH = 512;
|
||||
static constexpr int ATLAS_HEIGHT = 512;
|
||||
// stb_rect_pack 上下文 - 持久化以支持增量打包
|
||||
mutable stbrp_context packContext_;
|
||||
mutable std::vector<stbrp_node> packNodes_;
|
||||
|
||||
// 预分配缓冲区,避免每次动态分配
|
||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
||||
|
||||
// 初始化字体
|
||||
bool initFont(const std::string& filepath);
|
||||
// 渲染字形到图集
|
||||
bool renderGlyph(char32_t codepoint);
|
||||
// 更新图集纹理
|
||||
// 创建空白图集纹理
|
||||
void createAtlas();
|
||||
// 缓存字形到图集
|
||||
void cacheGlyph(char32_t codepoint);
|
||||
// 更新图集纹理区域
|
||||
void updateAtlas(int x, int y, int width, int height,
|
||||
const std::vector<uint8_t>& data);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ private:
|
|||
// 着色器和矩阵
|
||||
Ptr<IShader> shader_;
|
||||
uint32_t drawCallCount_;
|
||||
glm::mat4 viewProjection_;
|
||||
|
||||
// 内部方法
|
||||
void flush();
|
||||
|
|
|
|||
|
|
@ -17,54 +17,60 @@ namespace extra2d {
|
|||
|
||||
// ============================================================================
|
||||
// GLFontAtlas 构造函数
|
||||
// 使用 STB 库加载字体并创建纹理图集
|
||||
// 加载字体文件并初始化图集
|
||||
// ============================================================================
|
||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
||||
: useSDF_(useSDF), fontSize_(fontSize),
|
||||
lineHeight_(0.0f), ascent_(0.0f), descent_(0.0f), lineGap_(0.0f),
|
||||
scale_(0.0f) {
|
||||
: useSDF_(useSDF), fontSize_(fontSize), lineHeight_(0.0f), ascent_(0.0f),
|
||||
descent_(0.0f), lineGap_(0.0f), scale_(0.0f) {
|
||||
|
||||
// 加载字体文件
|
||||
if (!initFont(filepath)) {
|
||||
E2D_LOG_ERROR("Failed to initialize font: {}", filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建纹理图集 (使用单通道格式)
|
||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT, nullptr, 1);
|
||||
// 计算字体缩放比例和度量
|
||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
||||
|
||||
// 预加载 ASCII 字符
|
||||
std::string asciiChars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
||||
for (char c : asciiChars) {
|
||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||
renderGlyph(codepoint);
|
||||
}
|
||||
|
||||
// 计算字体度量
|
||||
int ascent, descent, lineGap;
|
||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
||||
|
||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize));
|
||||
|
||||
ascent_ = static_cast<float>(ascent) * scale_;
|
||||
descent_ = static_cast<float>(descent) * scale_;
|
||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
||||
lineHeight_ = ascent_ - descent_ + lineGap_;
|
||||
|
||||
// 创建图集纹理和打包上下文
|
||||
createAtlas();
|
||||
|
||||
// 预加载常用 ASCII 字符
|
||||
std::string asciiChars = " !\"#$%&'()*+,-./"
|
||||
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
|
||||
"abcdefghijklmnopqrstuvwxyz{|}~";
|
||||
for (char c : asciiChars) {
|
||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||
cacheGlyph(codepoint);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Font atlas created: {} ({}px, {}x{})", filepath, fontSize_,
|
||||
ATLAS_WIDTH, ATLAS_HEIGHT);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GLFontAtlas 析构函数
|
||||
// ============================================================================
|
||||
GLFontAtlas::~GLFontAtlas() {
|
||||
// STB 不需要显式清理,字体数据由 vector 自动管理
|
||||
// 智能指针自动管理纹理资源
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 获取字形信息
|
||||
// 获取字形信息 - 如果字形不存在则动态缓存
|
||||
// ============================================================================
|
||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
||||
auto it = glyphs_.find(codepoint);
|
||||
if (it == glyphs_.end()) {
|
||||
// 尝试渲染该字符
|
||||
const_cast<GLFontAtlas *>(this)->renderGlyph(codepoint);
|
||||
// 动态缓存新字形
|
||||
const_cast<GLFontAtlas *>(this)->cacheGlyph(codepoint);
|
||||
it = glyphs_.find(codepoint);
|
||||
if (it == glyphs_.end()) {
|
||||
return nullptr;
|
||||
|
|
@ -88,12 +94,13 @@ const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// 测量文本尺寸
|
||||
// 测量文本尺寸 - 支持多行文本
|
||||
// ============================================================================
|
||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
||||
float width = 0.0f;
|
||||
float maxWidth = 0.0f;
|
||||
float height = lineHeight_;
|
||||
float currentWidth = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < text.length();) {
|
||||
// 处理 UTF-8 编码
|
||||
|
|
@ -106,19 +113,23 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|||
i++;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
// 2字节 UTF-8
|
||||
if (i + 1 >= text.length()) break;
|
||||
codepoint = ((c & 0x1F) << 6) | (static_cast<unsigned char>(text[i + 1]) & 0x3F);
|
||||
if (i + 1 >= text.length())
|
||||
break;
|
||||
codepoint =
|
||||
((c & 0x1F) << 6) | (static_cast<unsigned char>(text[i + 1]) & 0x3F);
|
||||
i += 2;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
// 3字节 UTF-8
|
||||
if (i + 2 >= text.length()) break;
|
||||
if (i + 2 >= text.length())
|
||||
break;
|
||||
codepoint = ((c & 0x0F) << 12) |
|
||||
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 6) |
|
||||
(static_cast<unsigned char>(text[i + 2]) & 0x3F);
|
||||
i += 3;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
// 4字节 UTF-8
|
||||
if (i + 3 >= text.length()) break;
|
||||
if (i + 3 >= text.length())
|
||||
break;
|
||||
codepoint = ((c & 0x07) << 18) |
|
||||
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 12) |
|
||||
((static_cast<unsigned char>(text[i + 2]) & 0x3F) << 6) |
|
||||
|
|
@ -132,24 +143,24 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|||
|
||||
// 处理换行
|
||||
if (codepoint == '\n') {
|
||||
maxWidth = std::max(maxWidth, width);
|
||||
width = 0.0f;
|
||||
maxWidth = std::max(maxWidth, currentWidth);
|
||||
currentWidth = 0.0f;
|
||||
height += lineHeight_;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Glyph *glyph = getGlyph(codepoint);
|
||||
if (glyph) {
|
||||
width += glyph->advance;
|
||||
currentWidth += glyph->advance;
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = std::max(maxWidth, width);
|
||||
maxWidth = std::max(maxWidth, currentWidth);
|
||||
return Vec2(maxWidth, height);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 初始化字体 (使用 STB)
|
||||
// 初始化字体 - 加载字体文件到内存
|
||||
// ============================================================================
|
||||
bool GLFontAtlas::initFont(const std::string &filepath) {
|
||||
// 读取字体文件到内存
|
||||
|
|
@ -178,97 +189,203 @@ bool GLFontAtlas::initFont(const std::string &filepath) {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// 渲染字形到图集
|
||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
||||
// ============================================================================
|
||||
bool GLFontAtlas::renderGlyph(char32_t codepoint) {
|
||||
void GLFontAtlas::createAtlas() {
|
||||
// 统一使用 4 通道格式 (RGBA)
|
||||
int channels = 4;
|
||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
||||
emptyData.data(), channels);
|
||||
texture_->setFilter(true);
|
||||
|
||||
// 初始化矩形打包上下文 - 持久化以支持增量打包
|
||||
packNodes_.resize(ATLAS_WIDTH);
|
||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
||||
ATLAS_WIDTH);
|
||||
|
||||
// 预分配字形缓冲区
|
||||
// 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA)
|
||||
size_t maxGlyphSize = static_cast<size_t>(fontSize_ * fontSize_ * 4 * 4);
|
||||
glyphBitmapCache_.reserve(maxGlyphSize);
|
||||
glyphRgbaCache_.reserve(maxGlyphSize);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 缓存字形 - 渲染字形到图集并存储信息
|
||||
// 使用 stb_rect_pack 进行矩形打包
|
||||
// ============================================================================
|
||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) {
|
||||
// 检查是否已存在
|
||||
if (glyphs_.find(codepoint) != glyphs_.end()) {
|
||||
return true; // 已存在
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取字形索引
|
||||
int glyphIndex = stbtt_FindGlyphIndex(&fontInfo_, static_cast<int>(codepoint));
|
||||
if (glyphIndex == 0) {
|
||||
return false; // 字符不存在
|
||||
}
|
||||
|
||||
// 获取字形位图尺寸
|
||||
int width, height, xoff, yoff;
|
||||
unsigned char *bitmap = stbtt_GetCodepointBitmap(
|
||||
&fontInfo_, 0, scale_, static_cast<int>(codepoint),
|
||||
&width, &height, &xoff, &yoff);
|
||||
|
||||
if (!bitmap) {
|
||||
// 空白字符(如空格)
|
||||
// 获取字形水平度量
|
||||
int advance, leftSideBearing;
|
||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance, &leftSideBearing);
|
||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
||||
&leftSideBearing);
|
||||
float advancePx = static_cast<float>(advance) * scale_;
|
||||
|
||||
GlyphData data;
|
||||
data.width = 0;
|
||||
data.height = 0;
|
||||
data.bearingX = 0;
|
||||
data.bearingY = 0;
|
||||
data.advance = static_cast<float>(advance) * scale_;
|
||||
data.u0 = data.v0 = data.u1 = data.v1 = 0;
|
||||
// SDF 渲染模式
|
||||
if (useSDF_) {
|
||||
constexpr int SDF_PADDING = 8;
|
||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
||||
constexpr float PIXEL_DIST_SCALE = 64.0f;
|
||||
|
||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
||||
|
||||
if (!sdf || w <= 0 || h <= 0) {
|
||||
if (sdf)
|
||||
stbtt_FreeSDF(sdf, nullptr);
|
||||
// 创建空白字形(如空格)
|
||||
GlyphData data{};
|
||||
data.advance = advancePx;
|
||||
glyphs_[codepoint] = data;
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 stb_rect_pack 进行图集打包
|
||||
stbrp_context context;
|
||||
stbrp_node nodes[ATLAS_WIDTH];
|
||||
stbrp_init_target(&context, ATLAS_WIDTH, ATLAS_HEIGHT, nodes, ATLAS_WIDTH);
|
||||
|
||||
// 使用 stb_rect_pack 打包矩形
|
||||
stbrp_rect rect;
|
||||
rect.id = static_cast<int>(codepoint);
|
||||
rect.w = static_cast<stbrp_coord>(width);
|
||||
rect.h = static_cast<stbrp_coord>(height);
|
||||
rect.w = w + PADDING * 2;
|
||||
rect.h = h + PADDING * 2;
|
||||
|
||||
if (!stbrp_pack_rects(&context, &rect, 1) || !rect.was_packed) {
|
||||
E2D_LOG_WARN("Failed to pack glyph for codepoint: {}", codepoint);
|
||||
stbtt_FreeBitmap(bitmap, nullptr);
|
||||
return false;
|
||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
||||
if (!rect.was_packed) {
|
||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
|
||||
stbtt_FreeSDF(sdf, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int x = rect.x;
|
||||
int y = rect.y;
|
||||
int atlasX = rect.x + PADDING;
|
||||
int atlasY = rect.y + PADDING;
|
||||
|
||||
// 更新纹理
|
||||
updateAtlas(x, y, width, height, std::vector<uint8_t>(bitmap, bitmap + width * height));
|
||||
stbtt_FreeBitmap(bitmap, nullptr);
|
||||
|
||||
// 获取字形度量
|
||||
int advance, leftSideBearing;
|
||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance, &leftSideBearing);
|
||||
|
||||
int ix0, iy0, ix1, iy1;
|
||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_, scale_, &ix0, &iy0, &ix1, &iy1);
|
||||
|
||||
// 存储字形数据
|
||||
// 创建字形数据
|
||||
GlyphData data;
|
||||
data.width = static_cast<float>(width);
|
||||
data.height = static_cast<float>(height);
|
||||
data.width = static_cast<float>(w);
|
||||
data.height = static_cast<float>(h);
|
||||
data.bearingX = static_cast<float>(xoff);
|
||||
data.bearingY = static_cast<float>(-yoff); // STB 使用不同的坐标系
|
||||
data.advance = static_cast<float>(advance) * scale_;
|
||||
data.bearingY = static_cast<float>(yoff);
|
||||
data.advance = advancePx;
|
||||
|
||||
// 计算 UV 坐标
|
||||
data.u0 = static_cast<float>(x) / ATLAS_WIDTH;
|
||||
data.v0 = static_cast<float>(y) / ATLAS_HEIGHT;
|
||||
data.u1 = static_cast<float>(x + width) / ATLAS_WIDTH;
|
||||
data.v1 = static_cast<float>(y + height) / ATLAS_HEIGHT;
|
||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
||||
// 需要翻转V坐标
|
||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
||||
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
||||
data.v0 = 1.0f - v1; // 翻转V坐标
|
||||
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
||||
data.v1 = 1.0f - v0; // 翻转V坐标
|
||||
|
||||
glyphs_[codepoint] = data;
|
||||
return true;
|
||||
|
||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
||||
glyphRgbaCache_.resize(pixelCount * 4);
|
||||
for (size_t i = 0; i < pixelCount; ++i) {
|
||||
uint8_t alpha = sdf[i];
|
||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
||||
}
|
||||
|
||||
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
||||
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
||||
|
||||
stbtt_FreeSDF(sdf, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通位图渲染模式
|
||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
||||
scale_, &x0, &y0, &x1, &y1);
|
||||
int w = x1 - x0;
|
||||
int h = y1 - y0;
|
||||
int xoff = x0;
|
||||
// y0 是相对于基线的偏移(通常为负值,表示在基线上方)
|
||||
// bearingY 应该是字形顶部相对于基线的偏移
|
||||
int yoff = y0;
|
||||
|
||||
if (w <= 0 || h <= 0) {
|
||||
// 空白字符(如空格)
|
||||
GlyphData data{};
|
||||
data.advance = advancePx;
|
||||
glyphs_[codepoint] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用预分配缓冲区渲染字形
|
||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
||||
glyphBitmapCache_.resize(pixelCount);
|
||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
||||
scale_, scale_, static_cast<int>(codepoint));
|
||||
|
||||
// 使用 stb_rect_pack 打包矩形
|
||||
stbrp_rect rect;
|
||||
rect.id = static_cast<int>(codepoint);
|
||||
rect.w = w + PADDING * 2;
|
||||
rect.h = h + PADDING * 2;
|
||||
|
||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
||||
|
||||
if (!rect.was_packed) {
|
||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
|
||||
return;
|
||||
}
|
||||
|
||||
int atlasX = rect.x + PADDING;
|
||||
int atlasY = rect.y + PADDING;
|
||||
|
||||
// 创建字形数据
|
||||
GlyphData data;
|
||||
data.width = static_cast<float>(w);
|
||||
data.height = static_cast<float>(h);
|
||||
data.bearingX = static_cast<float>(xoff);
|
||||
data.bearingY = static_cast<float>(yoff);
|
||||
data.advance = advancePx;
|
||||
|
||||
// 计算 UV 坐标
|
||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
||||
// 需要翻转V坐标
|
||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
||||
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
||||
data.v0 = 1.0f - v1; // 翻转V坐标
|
||||
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
||||
data.v1 = 1.0f - v0; // 翻转V坐标
|
||||
|
||||
glyphs_[codepoint] = data;
|
||||
|
||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
||||
glyphRgbaCache_.resize(pixelCount * 4);
|
||||
for (size_t i = 0; i < pixelCount; ++i) {
|
||||
uint8_t alpha = glyphBitmapCache_[i];
|
||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
||||
}
|
||||
|
||||
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
||||
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 更新图集纹理
|
||||
// 更新图集纹理区域
|
||||
// ============================================================================
|
||||
void GLFontAtlas::updateAtlas(int x, int y, int width, int height,
|
||||
const std::vector<uint8_t> &data) {
|
||||
if (texture_) {
|
||||
texture_->bind();
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RED,
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data.data());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -599,15 +599,21 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
float penX = cursorX;
|
||||
cursorX += glyph->advance;
|
||||
|
||||
if (glyph->width <= 0.0f || glyph->height <= 0.0f) {
|
||||
// 使用 epsilon 比较浮点数,避免精度问题
|
||||
constexpr float EPSILON = 0.001f;
|
||||
if (glyph->width < EPSILON || glyph->height < EPSILON) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算字形位置
|
||||
// bearingX: 水平偏移(从左边缘到字形左边缘)
|
||||
// bearingY: 垂直偏移(从基线到字形顶部,通常为负值)
|
||||
float xPos = penX + glyph->bearingX;
|
||||
float yPos = baselineY + glyph->bearingY;
|
||||
|
||||
SpriteData data;
|
||||
data.position = Vec2(xPos, yPos);
|
||||
// 设置精灵中心位置(精灵批处理使用中心点)
|
||||
data.position = Vec2(xPos + glyph->width * 0.5f, yPos + glyph->height * 0.5f);
|
||||
data.size = Vec2(glyph->width, glyph->height);
|
||||
data.uvRect = Rect(
|
||||
Vec2(glyph->u0, glyph->v0),
|
||||
|
|
@ -615,7 +621,8 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
);
|
||||
data.color = color;
|
||||
data.rotation = 0.0f;
|
||||
data.pivot = Vec2(0.0f, 0.0f);
|
||||
// pivot (0.5, 0.5) 表示中心点,这样 position 就是精灵中心
|
||||
data.pivot = Vec2(0.5f, 0.5f);
|
||||
|
||||
sprites.push_back(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|||
batches_.clear();
|
||||
currentTexture_ = nullptr;
|
||||
drawCallCount_ = 0;
|
||||
// 保存 viewProjection 矩阵供后续使用
|
||||
viewProjection_ = viewProjection;
|
||||
}
|
||||
|
||||
void GLSpriteBatch::end() {
|
||||
|
|
@ -141,8 +143,15 @@ void GLSpriteBatch::submitBatch() {
|
|||
// 绑定着色器并设置uniform
|
||||
if (shader_) {
|
||||
shader_->bind();
|
||||
// 注意:batch 层不存储 viewProjection,需要外部传入
|
||||
// 这里简化处理,实际应该在 begin 时设置
|
||||
// 设置视图投影矩阵
|
||||
shader_->setMat4("u_viewProjection", viewProjection_);
|
||||
// 设置模型矩阵为单位矩阵(精灵位置已经在顶点生成时计算)
|
||||
glm::mat4 model(1.0f);
|
||||
shader_->setMat4("u_model", model);
|
||||
// 设置纹理采样器
|
||||
shader_->setInt("u_texture", 0);
|
||||
// 设置透明度
|
||||
shader_->setFloat("u_opacity", 1.0f);
|
||||
}
|
||||
|
||||
// 上传顶点数据
|
||||
|
|
@ -172,15 +181,6 @@ void GLSpriteBatch::flush() {
|
|||
submitBatch();
|
||||
}
|
||||
|
||||
// 绑定着色器并设置uniform
|
||||
if (shader_) {
|
||||
shader_->bind();
|
||||
// 这里需要从某处获取 viewProjection
|
||||
// 简化处理:假设 shader 已经在 begin 时设置了 viewProjection
|
||||
shader_->setInt("u_texture", 0);
|
||||
shader_->setFloat("u_opacity", 1.0f);
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
batches_.clear();
|
||||
currentTexture_ = nullptr;
|
||||
|
|
|
|||
|
|
@ -169,15 +169,20 @@ void SpriteBatch::generateVertices(const SpriteData& sprite, size_t vertexOffset
|
|||
}
|
||||
|
||||
// 设置纹理坐标
|
||||
float u1 = sprite.uvRect.origin.x;
|
||||
float v1 = sprite.uvRect.origin.y;
|
||||
float u2 = u1 + sprite.uvRect.size.width;
|
||||
float v2 = v1 + sprite.uvRect.size.height;
|
||||
// uvRect.origin = (u0, v0) - 左下
|
||||
// uvRect.size = (width, height) - 从左上到右下的尺寸
|
||||
float u0 = sprite.uvRect.origin.x;
|
||||
float v0 = sprite.uvRect.origin.y;
|
||||
float u1 = u0 + sprite.uvRect.size.width;
|
||||
float v1 = v0 + sprite.uvRect.size.height;
|
||||
|
||||
vertices_[vertexOffset + 0].texCoord = Vec2(u1, v2); // 左下
|
||||
vertices_[vertexOffset + 1].texCoord = Vec2(u2, v2); // 右下
|
||||
vertices_[vertexOffset + 2].texCoord = Vec2(u2, v1); // 右上
|
||||
vertices_[vertexOffset + 3].texCoord = Vec2(u1, v1); // 左上
|
||||
// 顶点顺序: 左下, 右下, 右上, 左上
|
||||
// 注意: 在 gl_font_atlas 中 v0 > v1 (因为翻转了V坐标)
|
||||
// 所以 v0 对应底部,v1 对应顶部
|
||||
vertices_[vertexOffset + 0].texCoord = Vec2(u0, v0); // 左下
|
||||
vertices_[vertexOffset + 1].texCoord = Vec2(u1, v0); // 右下
|
||||
vertices_[vertexOffset + 2].texCoord = Vec2(u1, v1); // 右上
|
||||
vertices_[vertexOffset + 3].texCoord = Vec2(u0, v1); // 左上
|
||||
|
||||
// 设置颜色
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue