feat(渲染): 添加SDF字体渲染支持
实现基于有符号距离场(SDF)的高质量字体渲染功能,包括: 1. 新增SDF专用着色器及配置文件 2. 扩展着色器管理器支持vec2/vec3类型uniform 3. 修改精灵批处理系统以支持自定义着色器 4. 更新渲染器实现SDF字体特殊处理逻辑 5. 替换示例中的字体资源为SDF字体
This commit is contained in:
parent
c32c2dd60d
commit
080fb56003
|
|
@ -134,6 +134,7 @@ private:
|
|||
IWindow* window_;
|
||||
GLSpriteBatch spriteBatch_;
|
||||
Ptr<IShader> shapeShader_;
|
||||
Ptr<IShader> sdfFontShader_; // SDF字体专用着色器
|
||||
|
||||
GLuint shapeVao_; // 形状 VAO(手动管理,用于顶点属性配置)
|
||||
GLBuffer shapeBuffer_; // 形状 VBO(使用 GLBuffer 管理)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||
#include <extra2d/graphics/shader/shader_interface.h>
|
||||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <vector>
|
||||
|
|
@ -27,6 +28,9 @@ public:
|
|||
void begin(const glm::mat4 &viewProjection);
|
||||
void end();
|
||||
|
||||
// 使用指定着色器开始批处理
|
||||
void begin(const glm::mat4 &viewProjection, Ptr<IShader> shader);
|
||||
|
||||
// 绘制单个精灵
|
||||
void draw(const Texture &texture, const SpriteData &data);
|
||||
|
||||
|
|
@ -37,6 +41,16 @@ public:
|
|||
// 获取绘制调用次数
|
||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||
|
||||
// 设置自定义着色器(用于SDF字体等特殊渲染)
|
||||
void setShader(Ptr<IShader> shader);
|
||||
|
||||
// 获取当前着色器
|
||||
Ptr<IShader> getShader() const { return shader_; }
|
||||
|
||||
// 设置额外的uniform值(用于SDF字体等特殊渲染)
|
||||
void setExtraUniforms(const UniformValueMap &uniforms) { extraUniforms_ = uniforms; }
|
||||
void clearExtraUniforms() { extraUniforms_.clear(); }
|
||||
|
||||
private:
|
||||
// OpenGL 对象
|
||||
GLuint vao_;
|
||||
|
|
@ -60,6 +74,9 @@ private:
|
|||
uint32_t drawCallCount_;
|
||||
glm::mat4 viewProjection_;
|
||||
|
||||
// 额外的uniform值(用于SDF字体等特殊渲染)
|
||||
UniformValueMap extraUniforms_;
|
||||
|
||||
// 内部方法
|
||||
void flush();
|
||||
void submitBatch();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ struct ShaderUniformDef {
|
|||
std::string type;
|
||||
std::string description;
|
||||
float defaultValue = 0.0f; // 默认值(用于float类型)
|
||||
float defaultVec2[2] = {0, 0}; // 默认值(用于vec2类型)
|
||||
float defaultVec3[3] = {0, 0, 0}; // 默认值(用于vec3类型)
|
||||
float defaultVec4[4] = {0, 0, 0, 0}; // 默认值(用于vec4类型)
|
||||
float defaultMat4[16] = {
|
||||
1, 0, 0, 0, 0, 1, 0, 0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 v_texCoord;
|
||||
in vec4 v_color;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform float u_opacity;
|
||||
uniform float u_sdfThreshold;
|
||||
uniform float u_sdfSmoothness;
|
||||
uniform vec2 u_textureSize;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
// 采样 SDF 纹理(SDF 值存储在 alpha 通道,范围 0-255 已映射到 0-1)
|
||||
float sdfValue = texture(u_texture, v_texCoord).a;
|
||||
|
||||
// 使用 fwidth 计算屏幕空间的变化率
|
||||
float fw = fwidth(sdfValue);
|
||||
|
||||
// 平衡的抗锯齿:根据屏幕空间变化率调整平滑范围
|
||||
// 在放大时更平滑,缩小时更锐利
|
||||
float smoothRange = max(u_sdfSmoothness, fw * 0.5);
|
||||
|
||||
// 使用 smoothstep 进行抗锯齿
|
||||
float alpha = smoothstep(u_sdfThreshold - smoothRange,
|
||||
u_sdfThreshold + smoothRange,
|
||||
sdfValue);
|
||||
|
||||
// 应用颜色和透明度
|
||||
fragColor = v_color;
|
||||
fragColor.a *= alpha * u_opacity;
|
||||
|
||||
// 丢弃完全透明的像素
|
||||
if (fragColor.a < 0.001) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "sdf_font",
|
||||
"category": "builtin",
|
||||
"version": "1.0",
|
||||
"description": "SDF字体渲染Shader,支持高质量抗锯齿",
|
||||
"uniforms": {
|
||||
"u_viewProjection": {
|
||||
"type": "mat4",
|
||||
"default": [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
],
|
||||
"description": "视图投影矩阵"
|
||||
},
|
||||
"u_model": {
|
||||
"type": "mat4",
|
||||
"default": [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
],
|
||||
"description": "模型矩阵"
|
||||
},
|
||||
"u_opacity": {
|
||||
"type": "float",
|
||||
"default": 1.0,
|
||||
"description": "透明度"
|
||||
},
|
||||
"u_sdfThreshold": {
|
||||
"type": "float",
|
||||
"default": 0.5,
|
||||
"description": "SDF阈值,默认0.5,范围0-1。值越小字形越粗,越大越细"
|
||||
},
|
||||
"u_sdfSmoothness": {
|
||||
"type": "float",
|
||||
"default": 0.02,
|
||||
"description": "SDF平滑度,控制抗锯齿范围,越小越锐利,默认0.02"
|
||||
},
|
||||
"u_textureSize": {
|
||||
"type": "vec2",
|
||||
"default": [512, 512],
|
||||
"description": "SDF纹理尺寸,用于计算像素级平滑"
|
||||
}
|
||||
},
|
||||
"samplers": {
|
||||
"u_texture": {
|
||||
"binding": 0,
|
||||
"description": "SDF纹理采样器"
|
||||
}
|
||||
},
|
||||
"backends": {
|
||||
"opengl": {
|
||||
"vertex": "backends/opengl/builtin/sprite.vert",
|
||||
"fragment": "backends/opengl/builtin/sdf_font.frag"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -165,6 +165,7 @@ void GLFontAtlas::createAtlas() {
|
|||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
||||
emptyData.data(), channels);
|
||||
// 所有字体都使用线性过滤,SDF的抗锯齿由着色器处理
|
||||
texture_->setFilter(true);
|
||||
|
||||
// 初始化矩形打包上下文 - 持久化以支持增量打包
|
||||
|
|
|
|||
|
|
@ -654,18 +654,42 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
float cursorY = y;
|
||||
float baselineY = cursorY + font.getAscent();
|
||||
|
||||
// 确保批处理已激活(自动批处理)
|
||||
if (autoBatchEnabled_) {
|
||||
ensureBatchActive();
|
||||
}
|
||||
// 检查是否为SDF字体
|
||||
bool isSDF = font.isSDF();
|
||||
|
||||
// 检查纹理变化,如果纹理不同则先提交当前批次
|
||||
if (autoBatchEnabled_ && currentBatchTexture_ != nullptr &&
|
||||
currentBatchTexture_ != font.getTexture()) {
|
||||
submitPendingSprites();
|
||||
}
|
||||
if (autoBatchEnabled_) {
|
||||
currentBatchTexture_ = font.getTexture();
|
||||
// 如果是SDF字体,切换到SDF着色器
|
||||
if (isSDF && sdfFontShader_) {
|
||||
// 先提交当前普通批处理
|
||||
if (autoBatchEnabled_ && !pendingSprites_.empty()) {
|
||||
submitPendingSprites();
|
||||
}
|
||||
|
||||
// 使用SDF着色器开始新的批处理
|
||||
// 设置需要动态计算的uniform值
|
||||
UniformValueMap sdfUniformValues;
|
||||
sdfUniformValues["u_viewProjection"] = viewProjection_;
|
||||
if (font.getTexture()) {
|
||||
sdfUniformValues["u_textureSize"] = ShaderUniformValue(
|
||||
glm::vec2(static_cast<float>(font.getTexture()->getWidth()),
|
||||
static_cast<float>(font.getTexture()->getHeight())));
|
||||
}
|
||||
// 设置额外的uniform值,让submitBatch使用
|
||||
spriteBatch_.setExtraUniforms(sdfUniformValues);
|
||||
spriteBatch_.begin(viewProjection_, sdfFontShader_);
|
||||
} else {
|
||||
// 确保批处理已激活(自动批处理)
|
||||
if (autoBatchEnabled_) {
|
||||
ensureBatchActive();
|
||||
}
|
||||
|
||||
// 检查纹理变化,如果纹理不同则先提交当前批次
|
||||
if (autoBatchEnabled_ && currentBatchTexture_ != nullptr &&
|
||||
currentBatchTexture_ != font.getTexture()) {
|
||||
submitPendingSprites();
|
||||
}
|
||||
if (autoBatchEnabled_) {
|
||||
currentBatchTexture_ = font.getTexture();
|
||||
}
|
||||
}
|
||||
|
||||
// 收集所有字符数据用于批处理
|
||||
|
|
@ -677,7 +701,10 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
if (codepoint == '\n') {
|
||||
// 换行时,将当前行添加到待处理列表
|
||||
if (!sprites.empty()) {
|
||||
if (autoBatchEnabled_) {
|
||||
if (isSDF && sdfFontShader_) {
|
||||
// SDF模式直接提交
|
||||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||
} else if (autoBatchEnabled_) {
|
||||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||
sprites.end());
|
||||
} else {
|
||||
|
|
@ -724,7 +751,7 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
sprites.push_back(data);
|
||||
|
||||
// 自动批处理:如果缓冲区满,先提交当前批次
|
||||
if (autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
|
||||
if (!isSDF && autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
|
||||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||
sprites.end());
|
||||
sprites.clear();
|
||||
|
|
@ -734,7 +761,10 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
|
||||
// 提交剩余的字符
|
||||
if (!sprites.empty()) {
|
||||
if (autoBatchEnabled_) {
|
||||
if (isSDF && sdfFontShader_) {
|
||||
// SDF模式直接提交
|
||||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||
} else if (autoBatchEnabled_) {
|
||||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||
sprites.end());
|
||||
} else {
|
||||
|
|
@ -742,6 +772,18 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是SDF字体,结束批处理并恢复普通着色器
|
||||
if (isSDF && sdfFontShader_) {
|
||||
spriteBatch_.end();
|
||||
// 清除额外的uniform值
|
||||
spriteBatch_.clearExtraUniforms();
|
||||
// 恢复默认的sprite着色器
|
||||
auto defaultShader = ShaderManager::getInstance().getBuiltin("sprite");
|
||||
if (defaultShader) {
|
||||
spriteBatch_.setShader(defaultShader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -762,6 +804,14 @@ void GLRenderer::initShapeRendering() {
|
|||
}
|
||||
}
|
||||
|
||||
// 加载SDF字体着色器
|
||||
sdfFontShader_ = ShaderManager::getInstance().getBuiltin("sdf_font");
|
||||
if (!sdfFontShader_) {
|
||||
E2D_LOG_WARN("获取SDF字体着色器失败,SDF字体将使用普通渲染");
|
||||
} else {
|
||||
E2D_LOG_INFO("SDF字体着色器加载成功");
|
||||
}
|
||||
|
||||
// 初始化形状 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新)
|
||||
BufferDesc shapeBufferDesc;
|
||||
shapeBufferDesc.type = BufferType::Vertex;
|
||||
|
|
@ -889,14 +939,15 @@ void GLRenderer::flushShapeBatch() {
|
|||
|
||||
if (shapeShader_) {
|
||||
shapeShader_->bind();
|
||||
|
||||
|
||||
// 只提供需要动态计算的值,其他值使用JSON中定义的默认值
|
||||
UniformValueMap uniformValues;
|
||||
uniformValues["u_viewProjection"] = viewProjection_;
|
||||
|
||||
|
||||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
||||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
||||
ShaderManager::getInstance().applyUniforms(shapeShader_, shapeShader_->getName(), uniformValues);
|
||||
ShaderManager::getInstance().applyUniforms(
|
||||
shapeShader_, shapeShader_->getName(), uniformValues);
|
||||
}
|
||||
|
||||
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||||
|
|
@ -925,14 +976,15 @@ void GLRenderer::flushLineBatch() {
|
|||
glLineWidth(currentLineWidth_);
|
||||
if (shapeShader_) {
|
||||
shapeShader_->bind();
|
||||
|
||||
|
||||
// 只提供需要动态计算的值,其他值使用JSON中定义的默认值
|
||||
UniformValueMap uniformValues;
|
||||
uniformValues["u_viewProjection"] = viewProjection_;
|
||||
|
||||
|
||||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
||||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
||||
ShaderManager::getInstance().applyUniforms(shapeShader_, shapeShader_->getName(), uniformValues);
|
||||
ShaderManager::getInstance().applyUniforms(
|
||||
shapeShader_, shapeShader_->getName(), uniformValues);
|
||||
}
|
||||
|
||||
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||||
|
|
|
|||
|
|
@ -93,6 +93,22 @@ void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|||
ebo_.bind();
|
||||
}
|
||||
|
||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection, Ptr<IShader> shader) {
|
||||
// 设置自定义着色器
|
||||
if (shader) {
|
||||
shader_ = shader;
|
||||
}
|
||||
begin(viewProjection);
|
||||
}
|
||||
|
||||
void GLSpriteBatch::setShader(Ptr<IShader> shader) {
|
||||
// 如果当前有未提交的批次,先提交
|
||||
if (batch_.getSpriteCount() > 0) {
|
||||
flush();
|
||||
}
|
||||
shader_ = shader;
|
||||
}
|
||||
|
||||
void GLSpriteBatch::end() {
|
||||
if (batch_.getSpriteCount() > 0) {
|
||||
flush();
|
||||
|
|
@ -166,6 +182,11 @@ void GLSpriteBatch::submitBatch() {
|
|||
UniformValueMap uniformValues;
|
||||
uniformValues["u_viewProjection"] = viewProjection_;
|
||||
|
||||
// 合并额外的uniform值(如SDF字体的u_textureSize)
|
||||
for (const auto& [name, value] : extraUniforms_) {
|
||||
uniformValues[name] = value;
|
||||
}
|
||||
|
||||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
||||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
||||
ShaderManager::getInstance().applyUniforms(shader_, shader_->getName(),
|
||||
|
|
|
|||
|
|
@ -549,6 +549,16 @@ Ptr<IShader> ShaderManager::loadFromMetadata(const std::string &jsonPath,
|
|||
def.defaultInt = value["default"].get<int>();
|
||||
} else if (def.type == "bool") {
|
||||
def.defaultBool = value["default"].get<bool>();
|
||||
} else if (def.type == "vec2" && value["default"].is_array()) {
|
||||
auto arr = value["default"].get<std::vector<float>>();
|
||||
for (size_t i = 0; i < arr.size() && i < 2; ++i) {
|
||||
def.defaultVec2[i] = arr[i];
|
||||
}
|
||||
} else if (def.type == "vec3" && value["default"].is_array()) {
|
||||
auto arr = value["default"].get<std::vector<float>>();
|
||||
for (size_t i = 0; i < arr.size() && i < 3; ++i) {
|
||||
def.defaultVec3[i] = arr[i];
|
||||
}
|
||||
} else if (def.type == "vec4" && value["default"].is_array()) {
|
||||
auto arr = value["default"].get<std::vector<float>>();
|
||||
for (size_t i = 0; i < arr.size() && i < 4; ++i) {
|
||||
|
|
@ -806,6 +816,12 @@ void ShaderManager::applyUniforms(Ptr<IShader> shader,
|
|||
shader->setInt(name, def.defaultInt);
|
||||
} else if (def.type == "bool") {
|
||||
shader->setBool(name, def.defaultBool);
|
||||
} else if (def.type == "vec2") {
|
||||
shader->setVec2(name,
|
||||
glm::vec2(def.defaultVec2[0], def.defaultVec2[1]));
|
||||
} else if (def.type == "vec3") {
|
||||
shader->setVec3(name, glm::vec3(def.defaultVec3[0], def.defaultVec3[1],
|
||||
def.defaultVec3[2]));
|
||||
} else if (def.type == "vec4") {
|
||||
shader->setVec4(name,
|
||||
glm::vec4(def.defaultVec4[0], def.defaultVec4[1],
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -23,17 +23,10 @@ public:
|
|||
}
|
||||
|
||||
try {
|
||||
font_ = renderer_->createFontAtlas("assets/fonts/arial.ttf", 24);
|
||||
font_ = renderer_->createFontAtlas("assets/fonts/fonts.ttf", 26, true);
|
||||
} catch (...) {
|
||||
std::cerr << "Failed to load font from assets/fonts/arial.ttf"
|
||||
std::cerr << "Failed to load font from assets/fonts/fonts.ttf"
|
||||
<< std::endl;
|
||||
// 尝试使用备用路径
|
||||
try {
|
||||
font_ = renderer_->createFontAtlas("C:/Windows/Fonts/arial.ttf", 24);
|
||||
} catch (...) {
|
||||
std::cerr << "Failed to load font from system path!" << std::endl;
|
||||
font_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (font_) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue