From 6b4ce696572b6d81520621847fbd46fd7390f734 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Tue, 17 Feb 2026 19:55:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E5=9B=BE=E9=9B=86=E5=92=8C=E7=B2=BE=E7=81=B5?= =?UTF-8?q?=E6=89=B9=E5=A4=84=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在GLFontAtlas中重构字体图集实现,使用stb_rect_pack进行动态矩形打包 - 增加图集尺寸至1024x1024并添加字形间距 - 改进字形UV计算和纹理坐标处理 - 在GLSpriteBatch中保存viewProjection矩阵并优化着色器uniform设置 - 修正文本渲染中的字形位置计算和精灵中心点处理 - 优化纹理坐标生成逻辑以正确处理UV翻转 --- 1.jpg | Bin 0 -> 75019 bytes .../graphics/backends/opengl/gl_font_atlas.h | 29 +- .../backends/opengl/gl_sprite_batch.h | 1 + .../backends/opengl/gl_font_atlas.cpp | 355 ++++++++++++------ .../graphics/backends/opengl/gl_renderer.cpp | 13 +- .../backends/opengl/gl_sprite_batch.cpp | 22 +- Extra2D/src/graphics/batch/sprite_batch.cpp | 21 +- 7 files changed, 289 insertions(+), 152 deletions(-) create mode 100644 1.jpg diff --git a/1.jpg b/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b75ea160d5327807041bc0a13eb30246b6e2df3d GIT binary patch literal 75019 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%0A|ECv4MlzL7Dq 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 E*_JdNM*si- literal 0 HcmV?d00001 diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h index 61c8288..72d286c 100644 --- a/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h @@ -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 texture_; std::unordered_map glyphs_; - std::unordered_map 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 packNodes_; + + // 预分配缓冲区,避免每次动态分配 + mutable std::vector glyphBitmapCache_; + mutable std::vector 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& data); }; diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h index 2679e6b..2808b00 100644 --- a/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h @@ -57,6 +57,7 @@ private: // 着色器和矩阵 Ptr shader_; uint32_t drawCallCount_; + glm::mat4 viewProjection_; // 内部方法 void flush(); diff --git a/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp b/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp index bb91b42..a578afe 100644 --- a/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp @@ -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(ATLAS_WIDTH, ATLAS_HEIGHT, nullptr, 1); + // 计算字体缩放比例和度量 + scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast(fontSize_)); - // 预加载 ASCII 字符 - std::string asciiChars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; - for (char c : asciiChars) { - char32_t codepoint = static_cast(static_cast(c)); - renderGlyph(codepoint); - } - - // 计算字体度量 int ascent, descent, lineGap; stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap); - - scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast(fontSize)); - + ascent_ = static_cast(ascent) * scale_; descent_ = static_cast(descent) * scale_; lineGap_ = static_cast(lineGap) * scale_; lineHeight_ = ascent_ - descent_ + lineGap_; + + // 创建图集纹理和打包上下文 + createAtlas(); + + // 预加载常用 ASCII 字符 + std::string asciiChars = " !\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~"; + for (char c : asciiChars) { + char32_t codepoint = static_cast(static_cast(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(this)->renderGlyph(codepoint); + // 动态缓存新字形 + const_cast(this)->cacheGlyph(codepoint); it = glyphs_.find(codepoint); if (it == glyphs_.end()) { return nullptr; @@ -88,37 +94,42 @@ 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 编码 char32_t codepoint = 0; unsigned char c = static_cast(text[i]); - + if ((c & 0x80) == 0) { // 单字节 ASCII codepoint = c; i++; } else if ((c & 0xE0) == 0xC0) { // 2字节 UTF-8 - if (i + 1 >= text.length()) break; - codepoint = ((c & 0x1F) << 6) | (static_cast(text[i + 1]) & 0x3F); + if (i + 1 >= text.length()) + break; + codepoint = + ((c & 0x1F) << 6) | (static_cast(text[i + 1]) & 0x3F); i += 2; } else if ((c & 0xF0) == 0xE0) { // 3字节 UTF-8 - if (i + 2 >= text.length()) break; - codepoint = ((c & 0x0F) << 12) | + if (i + 2 >= text.length()) + break; + codepoint = ((c & 0x0F) << 12) | ((static_cast(text[i + 1]) & 0x3F) << 6) | (static_cast(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(text[i + 1]) & 0x3F) << 12) | ((static_cast(text[i + 2]) & 0x3F) << 6) | @@ -129,27 +140,27 @@ Vec2 GLFontAtlas::measureText(const std::string &text) { i++; continue; } - + // 处理换行 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); + + 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) { - if (glyphs_.find(codepoint) != glyphs_.end()) { - return true; // 已存在 - } +void GLFontAtlas::createAtlas() { + // 统一使用 4 通道格式 (RGBA) + int channels = 4; + std::vector emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0); + texture_ = std::make_unique(ATLAS_WIDTH, ATLAS_HEIGHT, + emptyData.data(), channels); + texture_->setFilter(true); - // 获取字形索引 - int glyphIndex = stbtt_FindGlyphIndex(&fontInfo_, static_cast(codepoint)); - if (glyphIndex == 0) { - return false; // 字符不存在 - } + // 初始化矩形打包上下文 - 持久化以支持增量打包 + packNodes_.resize(ATLAS_WIDTH); + stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(), + ATLAS_WIDTH); - // 获取字形位图尺寸 - int width, height, xoff, yoff; - unsigned char *bitmap = stbtt_GetCodepointBitmap( - &fontInfo_, 0, scale_, static_cast(codepoint), - &width, &height, &xoff, &yoff); - - if (!bitmap) { - // 空白字符(如空格) - int advance, leftSideBearing; - stbtt_GetCodepointHMetrics(&fontInfo_, static_cast(codepoint), &advance, &leftSideBearing); - - GlyphData data; - data.width = 0; - data.height = 0; - data.bearingX = 0; - data.bearingY = 0; - data.advance = static_cast(advance) * scale_; - data.u0 = data.v0 = data.u1 = data.v1 = 0; - glyphs_[codepoint] = data; - return true; - } - - // 使用 stb_rect_pack 进行图集打包 - stbrp_context context; - stbrp_node nodes[ATLAS_WIDTH]; - stbrp_init_target(&context, ATLAS_WIDTH, ATLAS_HEIGHT, nodes, ATLAS_WIDTH); - - stbrp_rect rect; - rect.id = static_cast(codepoint); - rect.w = static_cast(width); - rect.h = static_cast(height); - - 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; - } - - int x = rect.x; - int y = rect.y; - - // 更新纹理 - updateAtlas(x, y, width, height, std::vector(bitmap, bitmap + width * height)); - stbtt_FreeBitmap(bitmap, nullptr); - - // 获取字形度量 - int advance, leftSideBearing; - stbtt_GetCodepointHMetrics(&fontInfo_, static_cast(codepoint), &advance, &leftSideBearing); - - int ix0, iy0, ix1, iy1; - stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast(codepoint), scale_, scale_, &ix0, &iy0, &ix1, &iy1); - - // 存储字形数据 - GlyphData data; - data.width = static_cast(width); - data.height = static_cast(height); - data.bearingX = static_cast(xoff); - data.bearingY = static_cast(-yoff); // STB 使用不同的坐标系 - data.advance = static_cast(advance) * scale_; - - // 计算 UV 坐标 - data.u0 = static_cast(x) / ATLAS_WIDTH; - data.v0 = static_cast(y) / ATLAS_HEIGHT; - data.u1 = static_cast(x + width) / ATLAS_WIDTH; - data.v1 = static_cast(y + height) / ATLAS_HEIGHT; - - glyphs_[codepoint] = data; - return true; + // 预分配字形缓冲区 + // 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA) + size_t maxGlyphSize = static_cast(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; + } + + // 获取字形水平度量 + int advance, leftSideBearing; + stbtt_GetCodepointHMetrics(&fontInfo_, static_cast(codepoint), &advance, + &leftSideBearing); + float advancePx = static_cast(advance) * scale_; + + // 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(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; + } + + // 使用 stb_rect_pack 打包矩形 + stbrp_rect rect; + rect.id = static_cast(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); + stbtt_FreeSDF(sdf, nullptr); + return; + } + + int atlasX = rect.x + PADDING; + int atlasY = rect.y + PADDING; + + // 创建字形数据 + GlyphData data; + data.width = static_cast(w); + data.height = static_cast(h); + data.bearingX = static_cast(xoff); + data.bearingY = static_cast(yoff); + data.advance = advancePx; + + // 计算 UV 坐标 + // stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点 + // 需要翻转V坐标 + float v0 = static_cast(atlasY) / ATLAS_HEIGHT; + float v1 = static_cast(atlasY + h) / ATLAS_HEIGHT; + data.u0 = static_cast(atlasX) / ATLAS_WIDTH; + data.v0 = 1.0f - v1; // 翻转V坐标 + data.u1 = static_cast(atlasX + w) / ATLAS_WIDTH; + data.v1 = 1.0f - v0; // 翻转V坐标 + + glyphs_[codepoint] = data; + + // 将 SDF 单通道数据转换为 RGBA 格式(统一格式) + size_t pixelCount = static_cast(w) * static_cast(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(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(w) * static_cast(h); + glyphBitmapCache_.resize(pixelCount); + stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w, + scale_, scale_, static_cast(codepoint)); + + // 使用 stb_rect_pack 打包矩形 + stbrp_rect rect; + rect.id = static_cast(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(w); + data.height = static_cast(h); + data.bearingX = static_cast(xoff); + data.bearingY = static_cast(yoff); + data.advance = advancePx; + + // 计算 UV 坐标 + // stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点 + // 需要翻转V坐标 + float v0 = static_cast(atlasY) / ATLAS_HEIGHT; + float v1 = static_cast(atlasY + h) / ATLAS_HEIGHT; + data.u0 = static_cast(atlasX) / ATLAS_WIDTH; + data.v0 = 1.0f - v1; // 翻转V坐标 + data.u1 = static_cast(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 &data) { + const std::vector &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()); } } diff --git a/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp index b34097a..11474b6 100644 --- a/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp @@ -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); } diff --git a/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp index 3ca1ef3..c605202 100644 --- a/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp @@ -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; diff --git a/Extra2D/src/graphics/batch/sprite_batch.cpp b/Extra2D/src/graphics/batch/sprite_batch.cpp index cd17b8b..45189ba 100644 --- a/Extra2D/src/graphics/batch/sprite_batch.cpp +++ b/Extra2D/src/graphics/batch/sprite_batch.cpp @@ -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) {