diff --git a/core/2d/Label.cpp b/core/2d/Label.cpp index 39c29e5af0dc..4432176f53e4 100644 --- a/core/2d/Label.cpp +++ b/core/2d/Label.cpp @@ -202,15 +202,15 @@ Label::BatchCommand::BatchCommand() textCommand.setPrimitiveType(CustomCommand::PrimitiveType::TRIANGLE); shadowCommand.setDrawType(CustomCommand::DrawType::ELEMENT); shadowCommand.setPrimitiveType(CustomCommand::PrimitiveType::TRIANGLE); - outLineCommand.setDrawType(CustomCommand::DrawType::ELEMENT); - outLineCommand.setPrimitiveType(CustomCommand::PrimitiveType::TRIANGLE); + effectCommand.setDrawType(CustomCommand::DrawType::ELEMENT); + effectCommand.setPrimitiveType(CustomCommand::PrimitiveType::TRIANGLE); } Label::BatchCommand::~BatchCommand() { AX_SAFE_RELEASE(textCommand.getPipelineDescriptor().programState); AX_SAFE_RELEASE(shadowCommand.getPipelineDescriptor().programState); - AX_SAFE_RELEASE(outLineCommand.getPipelineDescriptor().programState); + AX_SAFE_RELEASE(effectCommand.getPipelineDescriptor().programState); } void Label::BatchCommand::setProgramState(backend::ProgramState* programState) @@ -225,14 +225,14 @@ void Label::BatchCommand::setProgramState(backend::ProgramState* programState) AX_SAFE_RELEASE(programStateShadow); programStateShadow = programState->clone(); - auto& programStateOutline = outLineCommand.getPipelineDescriptor().programState; + auto& programStateOutline = effectCommand.getPipelineDescriptor().programState; AX_SAFE_RELEASE(programStateOutline); programStateOutline = programState->clone(); } std::array Label::BatchCommand::getCommandArray() { - return std::array{&textCommand, &shadowCommand, &outLineCommand}; + return std::array{&textCommand, &shadowCommand, &effectCommand}; } Label* Label::create() @@ -604,6 +604,7 @@ void Label::reset() TTFConfig temp; _fontConfig = temp; _outlineSize = 0.f; + _glowRadius = 0.f; _bmFontPath = ""; _bmSubTextureKey = ""; @@ -775,6 +776,7 @@ void Label::updateUniformLocations() _textureLocation = _programState->getUniformLocation(backend::Uniform::TEXTURE); _textColorLocation = _programState->getUniformLocation(backend::Uniform::TEXT_COLOR); _effectColorLocation = _programState->getUniformLocation(backend::Uniform::EFFECT_COLOR); + _effectWidthLocation = _programState->getUniformLocation(backend::Uniform::EFFECT_WIDTH); _passLocation = _programState->getUniformLocation(backend::Uniform::LABEL_PASS); _distanceSpreadLocation = _programState->getUniformLocation(backend::Uniform::DISTANCE_SPREAD); } @@ -1286,6 +1288,7 @@ bool Label::setTTFConfigInternal(const TTFConfig& ttfConfig) unsigned int mods = 0; mods |= (ttfConfig.fontSize != _fontConfig.fontSize); mods |= (ttfConfig.outlineSize != _fontConfig.outlineSize); + mods |= (ttfConfig.distanceFieldEnabled != _fontConfig.distanceFieldEnabled); _fontConfig = ttfConfig; return updateTTFConfigInternal(mods); } @@ -1383,17 +1386,17 @@ void Label::scaleFontSize(float fontSize) } } -void Label::enableGlow(const Color4B& glowColor) +void Label::enableGlow(const Color4B& glowColor, float glowRadius) { - if (_currentLabelType == LabelType::TTF) + if (glowRadius <= 0) + glowRadius = FontFreeType::DistanceMapSpread / 2.0f; + + if (_currentLabelType == LabelType::TTF && (glowRadius > 0 || _currLabelEffect == LabelEffect::GLOW)) { + _glowRadius = glowRadius; + auto config = _fontConfig; int mods = 0; - if (config.outlineSize > 0) - { - config.outlineSize = 0; - ++mods; - } // Note: axmol only support Glow effect in SDF rendering mode if (!_fontConfig.distanceFieldEnabled) { @@ -1958,9 +1961,9 @@ void Label::updateEffectUniforms(BatchCommand& batch, { pass = 2; Vec4 shadowColor = Vec4(_shadowColor4F.r, _shadowColor4F.g, _shadowColor4F.b, _shadowColor4F.a); - auto* programStateShadow = batch.shadowCommand.getPipelineDescriptor().programState; - programStateShadow->setUniform(_effectColorLocation, &shadowColor, sizeof(Vec4)); - programStateShadow->setUniform(_passLocation, &pass, sizeof(pass)); + auto shadowPS = batch.shadowCommand.getPipelineDescriptor().programState; + shadowPS->setUniform(_effectColorLocation, &shadowColor, sizeof(Vec4)); + shadowPS->setUniform(_passLocation, &pass, sizeof(pass)); batch.shadowCommand.init(_globalZOrder); renderer->addCommand(&batch.shadowCommand); } @@ -1970,22 +1973,23 @@ void Label::updateEffectUniforms(BatchCommand& batch, // outline pass { pass = 1; - effectColor.w = (_outlineSize > 0 ? _outlineSize : _fontConfig.outlineSize) * - _director->getContentScaleFactor(); - auto& outlinePS = batch.outLineCommand.getPipelineDescriptor().programState; - updateBuffer(textureAtlas, batch.outLineCommand); - outlinePS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); - outlinePS->setUniform(_passLocation, &pass, sizeof(pass)); + float outlineWidth = (_outlineSize > 0 ? _outlineSize : _fontConfig.outlineSize) * + _director->getContentScaleFactor(); float distanceFieldSpread = FontFreeType::DistanceMapSpread * _director->getContentScaleFactor(); - outlinePS->setUniform(_distanceSpreadLocation, &distanceFieldSpread, sizeof(distanceFieldSpread)); - batch.outLineCommand.init(_globalZOrder); - renderer->addCommand(&batch.outLineCommand); + auto effectPS = batch.effectCommand.getPipelineDescriptor().programState; + updateBuffer(textureAtlas, batch.effectCommand); + effectPS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); + effectPS->setUniform(_effectWidthLocation, &outlineWidth, sizeof(float)); + effectPS->setUniform(_distanceSpreadLocation, &distanceFieldSpread, sizeof(distanceFieldSpread)); + effectPS->setUniform(_passLocation, &pass, sizeof(pass)); + batch.effectCommand.init(_globalZOrder); + renderer->addCommand(&batch.effectCommand); } // text pass { pass = 0; - auto* textPS = batch.textCommand.getPipelineDescriptor().programState; + auto textPS = batch.textCommand.getPipelineDescriptor().programState; textPS->setUniform(_effectColorLocation, &effectColor, sizeof(effectColor)); textPS->setUniform(_passLocation, &pass, sizeof(pass)); @@ -1996,12 +2000,12 @@ void Label::updateEffectUniforms(BatchCommand& batch, // outline pass { pass = 1; - updateBuffer(textureAtlas, batch.outLineCommand); - auto* outlinePS = batch.outLineCommand.getPipelineDescriptor().programState; - outlinePS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); - outlinePS->setUniform(_passLocation, &pass, sizeof(pass)); - batch.outLineCommand.init(_globalZOrder); - renderer->addCommand(&batch.outLineCommand); + updateBuffer(textureAtlas, batch.effectCommand); + auto effectPS = batch.effectCommand.getPipelineDescriptor().programState; + effectPS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); + effectPS->setUniform(_passLocation, &pass, sizeof(pass)); + batch.effectCommand.init(_globalZOrder); + renderer->addCommand(&batch.effectCommand); } // text pass @@ -2020,8 +2024,8 @@ void Label::updateEffectUniforms(BatchCommand& batch, if (_shadowEnabled) { Vec4 shadowColor = Vec4(_shadowColor4F.r, _shadowColor4F.g, _shadowColor4F.b, _shadowColor4F.a); - auto* programStateShadow = batch.shadowCommand.getPipelineDescriptor().programState; - programStateShadow->setUniform(_textColorLocation, &shadowColor, sizeof(Vec4)); + auto shadowPS = batch.shadowCommand.getPipelineDescriptor().programState; + shadowPS->setUniform(_textColorLocation, &shadowColor, sizeof(Vec4)); batch.shadowCommand.init(_globalZOrder); renderer->addCommand(&batch.shadowCommand); } @@ -2029,20 +2033,42 @@ void Label::updateEffectUniforms(BatchCommand& batch, break; case LabelEffect::GLOW: { - // draw shadow + int pass = 0; + // shadow pass if (_shadowEnabled) { + pass = 2; Vec4 shadowColor = Vec4(_shadowColor4F.r, _shadowColor4F.g, _shadowColor4F.b, _shadowColor4F.a); - auto* programStateShadow = batch.shadowCommand.getPipelineDescriptor().programState; - programStateShadow->setUniform(_textColorLocation, &shadowColor, sizeof(Vec4)); - programStateShadow->setUniform(_effectColorLocation, &shadowColor, sizeof(Vec4)); + auto shadowPS = batch.shadowCommand.getPipelineDescriptor().programState; + shadowPS->setUniform(_textColorLocation, &shadowColor, sizeof(Vec4)); + shadowPS->setUniform(_effectColorLocation, &shadowColor, sizeof(Vec4)); + shadowPS->setUniform(_passLocation, &pass, sizeof(pass)); batch.shadowCommand.init(_globalZOrder); renderer->addCommand(&batch.shadowCommand); } + // glow pass + { + pass = 1; + const float glowRadius = _glowRadius * _director->getContentScaleFactor(); + const float distanceFieldSpread = FontFreeType::DistanceMapSpread * _director->getContentScaleFactor(); + Vec4 effectColor(_effectColorF.r, _effectColorF.g, _effectColorF.b, _effectColorF.a); + updateBuffer(textureAtlas, batch.effectCommand); + auto effectPS = batch.effectCommand.getPipelineDescriptor().programState; + effectPS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); + effectPS->setUniform(_effectWidthLocation, &glowRadius, sizeof(float)); + effectPS->setUniform(_distanceSpreadLocation, &distanceFieldSpread, sizeof(distanceFieldSpread)); + effectPS->setUniform(_passLocation, &pass, sizeof(pass)); + batch.effectCommand.init(_globalZOrder); + renderer->addCommand(&batch.effectCommand); + } + + // text pass + pass = 0; Vec4 effectColor(_effectColorF.r, _effectColorF.g, _effectColorF.b, _effectColorF.a); - batch.textCommand.getPipelineDescriptor().programState->setUniform(_effectColorLocation, &effectColor, - sizeof(Vec4)); + auto textPS = batch.textCommand.getPipelineDescriptor().programState; + textPS->setUniform(_effectColorLocation, &effectColor, sizeof(Vec4)); + textPS->setUniform(_passLocation, &pass, sizeof(pass)); } break; default: @@ -2151,7 +2177,7 @@ void Label::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) } batch.textCommand.getPipelineDescriptor().programState->setUniform(_mvpMatrixLocation, matrixMVP.m, sizeof(matrixMVP.m)); - batch.outLineCommand.getPipelineDescriptor().programState->setUniform(_mvpMatrixLocation, matrixMVP.m, + batch.effectCommand.getPipelineDescriptor().programState->setUniform(_mvpMatrixLocation, matrixMVP.m, sizeof(matrixMVP.m)); updateEffectUniforms(batch, textureAtlas, renderer, transform); } diff --git a/core/2d/Label.h b/core/2d/Label.h index d3df567dd5ea..cea20f9ccc71 100644 --- a/core/2d/Label.h +++ b/core/2d/Label.h @@ -56,7 +56,7 @@ typedef struct _ttfConfig GlyphCollection glyphs; float fontSize; // The desired render font size int faceSize; // The original face size of font, used when distanceFieldEnabled == true - int outlineSize; + int outlineSize; // The Outline width used in non‑SDF rendering; ignored when distance field is enabled bool distanceFieldEnabled; bool italics; @@ -442,7 +442,7 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol * Enable glow effect to Label. * @warning Limiting use to only when the Label created with true type font. */ - virtual void enableGlow(const Color4B& glowColor); + virtual void enableGlow(const Color4B& glowColor, float glowRadius = -1); /** * Enable italics rendering @@ -503,6 +503,8 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol */ float getOutlineSize() const { return _outlineSize; } + float getGlowRadius() const { return _glowRadius; } + /** * Return current effect type. */ @@ -759,7 +761,7 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol std::array getCommandArray(); CustomCommand textCommand; - CustomCommand outLineCommand; + CustomCommand effectCommand; // effect: outline or glow CustomCommand shadowCommand; }; @@ -857,6 +859,7 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol LabelType _currentLabelType; int _numberOfLines; float _outlineSize; + float _glowRadius; float _systemFontSize; int _lengthOfString; @@ -943,6 +946,7 @@ class AX_DLL Label : public Node, public LabelProtocol, public BlendProtocol backend::UniformLocation _textureLocation; backend::UniformLocation _textColorLocation; backend::UniformLocation _effectColorLocation; + backend::UniformLocation _effectWidthLocation; backend::UniformLocation _passLocation; backend::UniformLocation _distanceSpreadLocation; diff --git a/core/renderer/backend/ShaderModule.h b/core/renderer/backend/ShaderModule.h index 9a2e29fa27d2..a6cb52519351 100644 --- a/core/renderer/backend/ShaderModule.h +++ b/core/renderer/backend/ShaderModule.h @@ -48,6 +48,7 @@ enum Uniform : uint32_t TEXTURE3, TEXT_COLOR, EFFECT_COLOR, + EFFECT_WIDTH, LABEL_PASS, DISTANCE_SPREAD, UNIFORM_MAX // Maximum uniforms diff --git a/core/renderer/backend/Types.h b/core/renderer/backend/Types.h index af6ade0a2850..6fab1e242e3c 100644 --- a/core/renderer/backend/Types.h +++ b/core/renderer/backend/Types.h @@ -113,6 +113,7 @@ static constexpr auto UNIFORM_NAME_TEXTURE2 = "u_tex2"sv; static constexpr auto UNIFORM_NAME_TEXTURE3 = "u_tex3"sv; static constexpr auto UNIFORM_NAME_TEXT_COLOR = "u_textColor"sv; static constexpr auto UNIFORM_NAME_EFFECT_COLOR = "u_effectColor"sv; +static constexpr auto UNIFORM_NAME_EFFECT_WIDTH = "u_effectWidth"sv; static constexpr auto UNIFORM_NAME_LABEL_PASS = "u_labelPass"sv; static constexpr auto UNIFORM_NAME_DISTANCE_SPREAD = "u_distanceSpread"sv; diff --git a/core/renderer/backend/metal/ShaderModuleMTL.mm b/core/renderer/backend/metal/ShaderModuleMTL.mm index ea98d8c31355..1ce7ee0ab780 100644 --- a/core/renderer/backend/metal/ShaderModuleMTL.mm +++ b/core/renderer/backend/metal/ShaderModuleMTL.mm @@ -307,6 +307,9 @@ of this software and associated documentation files (the "Software"), to deal /// u_effectColor _builtinUniforms[Uniform::EFFECT_COLOR] = getUniformInfo(UNIFORM_NAME_EFFECT_COLOR); + /// u_effectWidth + _builtinUniforms[Uniform::EFFECT_WIDTH] = getUniformInfo(UNIFORM_NAME_EFFECT_WIDTH); + /// u_textPass _builtinUniforms[Uniform::LABEL_PASS] = getUniformInfo(UNIFORM_NAME_LABEL_PASS); diff --git a/core/renderer/backend/opengl/ProgramGL.cpp b/core/renderer/backend/opengl/ProgramGL.cpp index ecd9bec79690..920f9cff1de3 100644 --- a/core/renderer/backend/opengl/ProgramGL.cpp +++ b/core/renderer/backend/opengl/ProgramGL.cpp @@ -257,6 +257,9 @@ void ProgramGL::setBuiltinLocations() /// u_effectColor _builtinUniformLocation[Uniform::EFFECT_COLOR] = getUniformLocation(UNIFORM_NAME_EFFECT_COLOR); + /// u_effectWidth + _builtinUniformLocation[Uniform::EFFECT_WIDTH] = getUniformLocation(UNIFORM_NAME_EFFECT_WIDTH); + /// u_textPass _builtinUniformLocation[Uniform::LABEL_PASS] = getUniformLocation(UNIFORM_NAME_LABEL_PASS); diff --git a/core/renderer/shaders/label_distanceGlow.frag b/core/renderer/shaders/label_distanceGlow.frag index e6393eac9daf..7042cb36618b 100644 --- a/core/renderer/shaders/label_distanceGlow.frag +++ b/core/renderer/shaders/label_distanceGlow.frag @@ -1,6 +1,5 @@ #version 310 es precision highp float; - #include "base.glsl" layout(location = COLOR0) in vec4 v_color; @@ -9,8 +8,11 @@ layout(location = TEXCOORD0) in vec2 v_texCoord; layout(binding = 0) uniform sampler2D u_tex0; layout(std140) uniform fs_ub { - vec4 u_textColor; - vec4 u_effectColor; + vec4 u_textColor; // text color + vec4 u_effectColor; // effect color (rgb = color, a = intensity) + float u_effectWidth; // glow width in pixels + float u_distanceSpread;// default: 6.0 + int u_labelPass; // 0: text, 1: glow, 2: shadow }; layout(location = SV_Target0) out vec4 FragColor; @@ -18,10 +20,27 @@ layout(location = SV_Target0) out vec4 FragColor; void main() { float dist = texture(u_tex0, v_texCoord).x; - float smoothing = FWIDTH(dist); + float smoothing = fwidth(dist); - float alpha = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist); - float mu = smoothstep(0.5, 1.0, sqrt(dist)); - vec4 color = u_effectColor*(1.0-alpha) + u_textColor*alpha; - FragColor = v_color * vec4(color.rgb, max(alpha,mu)*color.a); + if (u_labelPass == 0) { + // Text pass: solid core + float alpha = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist); + FragColor = v_color * vec4(u_textColor.rgb, u_textColor.a * alpha); + } + else if (u_labelPass == 1) { + // Glow pass: soft halo around text + // Use distance field falloff to create smooth glow + // Map u_effectWidth (in "visual units") into SDF domain + float glowWidth = clamp(u_effectWidth / u_distanceSpread, 0.0, 1.0); + float pivot = 0.5 + (1.0 - glowWidth); + float alpha = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist); + float mu = smoothstep(0.5, pivot, sqrt(dist)); + vec4 color = u_effectColor * (1.0 - alpha) + u_textColor * alpha; + FragColor = v_color * vec4(color.rgb, max(alpha, mu) * color.a); + } + else { + // Shadow pass: simple fill + float alpha = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist); + FragColor = v_color * vec4(u_effectColor.rgb, u_effectColor.a * alpha); + } } diff --git a/core/renderer/shaders/label_distanceOutline.frag b/core/renderer/shaders/label_distanceOutline.frag index 3c5bc7f139a4..d88b0140c6da 100644 --- a/core/renderer/shaders/label_distanceOutline.frag +++ b/core/renderer/shaders/label_distanceOutline.frag @@ -15,8 +15,9 @@ layout(binding = 0) uniform sampler2D u_tex0; layout(std140) uniform fs_ub { vec4 u_textColor; vec4 u_effectColor; - int u_labelPass; // 0: text, 1: outline, 2: shadow + float u_effectWidth; // outline thickness in pixels float u_distanceSpread; // default: 6.0 + int u_labelPass; // 0: text, 1: outline, 2: shadow }; layout(location = SV_Target0) out vec4 FragColor; @@ -33,9 +34,9 @@ void main() } else if (u_labelPass == 1) { // Outline pass: only draw outer ring, exclude text core - float outlineSize = clamp(u_effectColor.w * outlineScale, 0.0, u_distanceSpread * 0.5); - float thickness = outlineSize / (2.0 * u_distanceSpread); - float pivot = 0.5 - thickness; + // clamp(u_effectWidth * outlineScale, 0.0, u_distanceSpread * 0.5); + float thickness = u_effectWidth * outlineScale; + float pivot = 0.5 - (thickness / (2.0 * u_distanceSpread)); float textAlpha = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist); float outlineAlpha = smoothstep(pivot - smoothing, pivot + smoothing, dist); diff --git a/tests/cpp-tests/Source/LabelTest/LabelTest.cpp b/tests/cpp-tests/Source/LabelTest/LabelTest.cpp index 7752c5c3ec1c..fabbbf1c7974 100644 --- a/tests/cpp-tests/Source/LabelTest/LabelTest.cpp +++ b/tests/cpp-tests/Source/LabelTest/LabelTest.cpp @@ -1470,9 +1470,21 @@ LabelTTFSDF::LabelTTFSDF() _labelSDF->enableOutline(Color4B::GREEN, size); _labelNormal->enableOutline(Color4B::GREEN, size); }); - initToggleCheckboxes(); _sliderOutline->setEnabled(false); _sliderOutline->setOpacity(100); + + _sliderGlow = initSlider("Glow", Vec2(size.width * 0.5, size.height * 0.35), + [=, this](Object* obj, ui::Slider::EventType type) { + Slider* slider = (Slider*)obj; + float size = 1 + slider->getPercent() / 10; + if (!slider->isEnabled()) + return; + _labelSDF->enableGlow(Color4B::RED, size); + _labelNormal->enableGlow(Color4B::RED, size); + }); + _sliderGlow->setEnabled(false); + _sliderGlow->setOpacity(100); + initToggleCheckboxes(); } ui::Slider* LabelTTFSDF::initSlider(std::string content, Vec2 pos, @@ -1511,7 +1523,7 @@ void LabelTTFSDF::initToggleCheckboxes() // Create the radio buttons static const int NUMBER_OF_BUTTONS = 3; startPosY = winSize.height * 0.25; - std::vector labelTypes = {"Normal", "Glow", "OutLine"}; + std::vector labelTypes = {"Normal", "Glow", "Outline"}; for (int i = 0; i < NUMBER_OF_BUTTONS; ++i) { @@ -1546,13 +1558,20 @@ void LabelTTFSDF::onChangedRadioButtonSelect(RadioButton* radioButton, RadioButt _sliderOutline->setEnabled(false); _sliderOutline->setOpacity(100); _sliderOutline->setPercent(0); + + _sliderGlow->setEnabled(false); + _sliderGlow->setOpacity(100); + _sliderGlow->setPercent(0); switch (radioButton->getTag()) { case 0: + break; case 1: - _labelNormal->enableGlow(Color4B::RED); - _labelSDF->enableGlow(Color4B::RED); + _labelNormal->enableGlow(Color4B::RED, 1); + _labelSDF->enableGlow(Color4B::RED, 1); + _sliderGlow->setEnabled(true); + _sliderGlow->setOpacity(255); break; case 2: _labelSDF->enableOutline(Color4B::GREEN, 1); diff --git a/tests/cpp-tests/Source/LabelTest/LabelTest.h b/tests/cpp-tests/Source/LabelTest/LabelTest.h index 3572c2c147ba..3abc85cb300a 100644 --- a/tests/cpp-tests/Source/LabelTest/LabelTest.h +++ b/tests/cpp-tests/Source/LabelTest/LabelTest.h @@ -374,7 +374,8 @@ class LabelTTFSDF : public AtlasDemoNew virtual std::string subtitle() const override; ax::Label* _labelNormal; ax::Label* _labelSDF; - ax::ui::Slider *_sliderOutline; + ax::ui::Slider* _sliderOutline; + ax::ui::Slider* _sliderGlow; void initToggleLabel(std::string content, ax::Vec2 pos, std::function callback); ax::ui::Slider* initSlider(std::string content,ax::Vec2 pos,std::function callback); void initToggleCheckboxes();