Skip to content

Add GlassStyle with internal tent blur support.#1507

Merged
Hparty merged 92 commits into
mainfrom
feature/fengweizou_glassstyle
Jul 23, 2026
Merged

Add GlassStyle with internal tent blur support.#1507
Hparty merged 92 commits into
mainfrom
feature/fengweizou_glassstyle

Conversation

@zfw1234567

@zfw1234567 zfw1234567 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

概述

本 PR 新增 GlassStyle 图层样式,提供背景磨砂、边缘折射、色散和边缘光照效果;同时新增仅供 GlassStyle 内部生成 AlphaMask UDF 使用的三角模糊实现。

主要改动

  • 新增公开 API GlassStyle

    • 支持折射、深度、磨砂、色散、扩散、光线角度和光线强度参数。
    • 默认参数对齐设计规范:折射 80、深度 20、磨砂 5、色散 50、扩散 0、光线角度 45°、光线强度 80。
    • 支持圆角矩形、椭圆和任意不规则形状。
  • 新增玻璃渲染流程

    1. 根据磨砂范围和最大折射位移扩展背景捕获范围。
    2. 捕获玻璃下方背景;按需要下采样,避免过大的中间纹理。
    3. 对捕获背景应用 Gaussian Blur,生成磨砂背景。
    4. 使用 AlphaMask UDF 折射路径计算折射位移与边缘光照。
    5. 在折射后的背景上合成色散和边缘光照结果。
  • 新增 AlphaMask / UDF 路径

    • 将任意形状内容缩放至受限分辨率,使用内部三角模糊生成 Fine UDF。
    • Fine UDF 用于计算高度梯度、折射方向与折射位移。
    • 使用低分辨率 Coarse UDF 计算边缘光照强度,复用 Fine UDF 的方向作为边缘光法线。
    • 统一支持圆角矩形、椭圆、星形、路径等所有形状类型。
  • 新增内部三角模糊

    • TentBlur1DFragmentProcessor 仅位于 src/layers/processors/,不作为公开 API。
    • 使用可分离双 pass 三角核生成 AlphaMask 的 Fine/Coarse UDF。
    • 使用 RGBA8 编解码保持 UDF 中间结果精度。
  • 测试

    • GlassStyleEllipseGlassStyleZoom 两个截图测试覆盖椭圆和缩放场景。

…nt normal, edge-band only refraction, actual maxDisp from two-pass computation.
…-style pipeline

- Replace GlassHighlightMap with GlassMaskEffect RGBA8 32-bit packing pass
- Generate UDF height map by Gaussian-blurring layer alpha, controlled by depth
- Implement Figma-style refraction: gradient direction, splay mixing, edge proximity offset
- Add GlassShaderFragments.h with modular SDF shaders (RoundedRect, Ellipse, Star, AlphaMask)
- Restore radial+axis direction mixing for analytical SDF shapes
- Add smooth attenuation to prevent boundary discontinuities
- Update GlassStyleSingle test to use star-shaped AlphaMask glass
- Add figma-glass-alpha-mask-pipeline documentation
…nd docs

- Add LiquidGlass builder with star-shaped AlphaMask glass + mouse drag support
- Add checker_128.png resource to Xcode project
- Add virtual setGlassPosition to LayerBuilder for mouse interaction
- Simplify ior to direct refractionFactor (remove unnecessary ior variable)
- Update blur sigma range to 0.2-0.4 based on depth
- Update refDist formula to halfW * (0.5*refractionFactor + 0.5*depthRatio)
- Use edge proximity (1-height) for AlphaMask offset distance
- Add glass-style-rendering-pipeline.md documentation
- Add public getType(), getRRect(), getRect(), getOval() to LayerContent
- Override getRRect() in RRectContent, getRect() in RectContent, getOval() in PathContent
- Add layerContent field to LayerStyleInput
- Pass getContent() into LayerStyleInput in Layer.cpp and BackgroundHandler.cpp
- GlassStyle auto-detects shape type: RRect→RoundedRect, oval Path→Ellipse, other→AlphaMask
- Update rendering pipeline documentation with detailed shader flow
- Replace 4-point central difference with 3x3 Sobel operator for smoother gradient direction
- Keep adaptive step size (minHalf/20 + 1) without clamp
- Temporarily set GlassStyleSingle test to refraction=1, depth=10 for debugging
- Fix RGBA8 pack shader: clamp value to 0.999999 before packing to avoid
  fract(1.0) precision issues that caused center regions to decode as 0.50
- Use 3x3 Sobel operator instead of 4-point central difference for smoother
  gradient direction in AlphaMask path
- Change blur sigma formula to minHalf * (depth/100) * 0.2
- Update GlassStyleSingle test params to refraction=70, depth=80
- Change offsetDist from refDist*edgeProximity to refractionFactor*depthRatio*edgeProximity*halfW
- Remove refDist variable, use halfW directly for clamp
- Update GlassStyleSingle test to refraction=50, depth=100, splay=100
Comment thread src/layers/layerstyles/GlassStyle.cpp Outdated
auto radii = rRect.radii();
// The analytical SDF currently supports a single scalar corner radius.
float representativeRadius = std::min(radii[0].x, radii[0].y);
crRadius = std::min(representativeRadius * effectiveContentScale, minHalf);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crRadius 乘了 effectiveContentScale(内容像素空间),但 shader 里 SDF 是在 layer 空间求值的:glassPixel = glassUV * (halfW2, halfH2),halfW/halfH 来自未缩放的 origBounds。contentScale != 1(zoom)时 SDF 圆角半径与实际形状不匹配,且与上方“zoom-invariant”的注释意图矛盾。建议直接用 layer 空间半径:std::min(representativeRadius, minHalf)(212 行 _cornerRadius 分支同理)。

// distance in layer space remains consistent regardless of UDF resolution.
float origMaxDim = std::max(origBounds.width(), origBounds.height());
if (origMaxDim <= 0.0f) {
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当 layerContent 为 nullptr(例如 group 图层)时 origBounds 为 0x0,走到这里 origMaxDim <= 0 直接 return,整个玻璃效果(包括已经计算好的 frost 背景)都会静默不绘制。建议回退用 input.content 的尺寸推导 origBounds,或至少继续绘制磨砂背景而不是直接返回。

}

/** Sets the corner radius for the glass surface shape. */
void setCornerRadius(float radius);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cornerRadius/setCornerRadius 目前是死代码:_cornerRadius 唯一的读取点(GlassStyle.cpp:212)算出的值在所有可达分支都会被覆盖——RRect 分支改用实际 radii、Rect 分支置 0、Ellipse/AlphaMask 不使用。公开 API 无任何实际效果,建议删除,或让它真正生效(例如作为内容无半径信息时的覆盖值)。


PlacementPtr<FragmentProcessor> sourceFragment = getSourceFragmentProcessor(
source, args.context, args.renderFlags, srcSampleBounds, Point(blurDstScaleX, blurDstScaleY));
const auto isAlphaOnly = source->isAlphaOnly();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blur 目标格式沿用了 source->isAlphaOnly(),但 TentBlur1D shader 输出的是 RGBA8 打包的 float。若源是 alpha-only(如 alpha8 图像内容),打包数据只剩 A 通道,第二 pass dot(rgba, UNPACK) 约等于 0,UDF/折射静默失效。打包路径应强制 RGBA8 目标(这里 isAlphaOnly 传 false)。

Comment thread test/src/LayerTest.cpp Outdated
root->addChild(container);
}

TGFX_TEST(LayerTest, GlassStyle) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

三个 GlassStyle 测试是同一套 7x3 参数扫描复制三遍,且解析 SDF 路径移除后 GlassShapeType 已只是测试侧的构造参数(三个测试走的是同一条 AlphaMask 路径),建议合并为一个参数化测试。另外所有用例都是 zoom=1:实现里多处依赖 contentScale/bgScale/udfScale 的 zoom-invariant 换算,缩放状态没有任何覆盖,坐标空间类问题无法被截图测试暴露。建议补一个缩放状态(如 setZoomScale(2))的 glass 截图用例。

const bool hasCoarseMask = (coarseMaskProxy != nullptr);
const bool hasDispersion = (params.dispersion >= 0.01f);
const bool hasLightIntensity = (params.lightIntensity > 0.0f);
const bool useAxisMix = (params.shapeType == GlassShapeType::RoundedRect ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

解析 SDF 路径从 GlassStyle 移除后,params.shapeType 在唯一生产处(getRefractionFilter)被硬编码为 AlphaMask,useAxisMix 相关的圆角矩形/椭圆 SDF 分支(约 100 行,含 81-106、126-134、158-198 行段)以及 GlassShapeType::RoundedRect/Ellipse 枚举值都已成为不可达死代码。这是被移除方案留下的脚手架,建议一并删掉:emitCode 只保留 AlphaMask 路径,GlassShapeType 枚举和 params.shapeType 字段也可以整体移除。

Comment thread src/layers/layerstyles/GlassStyle.cpp Outdated
float depthRatio = getDepthRatio();
float glassThickness = getGlassThickness(minHalf);
float analyticalOffset = glassThickness * refractionFactor;
// Match the shader's AlphaMask max displacement: halfW * refractionFactor * depthRatio *

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这轮 shader 已把 refractionDistance 改为 minHalf * refractionFactor * depthRatio * depthScale(edgeProximity 也从 h*(1-h) 改为 (1-h²)²),这里的注释还写的是 halfW,已过期。安全性没问题(minHalf <= halfW,outset 仍是上界),但建议同步更新注释;另外这个公式在 CPU 和 GLSL 各维护一份,已经是第二次漂移了,建议把 max displacement 的估算收敛到一处或加固定余量。

" vec2 centerDir = (centerDistance > 0.001) ? vec2(-px / centerDistance, -py / "
"centerDistance) : vec2(0.0);");
fragBuilder->codeAppend(" vec2 mixedDir = mix(gradientDir, centerDir, splay);");
fragBuilder->codeAppend(" mixedDir = normalize(mixedDir);");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里 normalize(mixedDir) 存在除零的未定义行为:splay=1 且像素恰好落在形状中心时 centerDir=vec2(0),mixedDir=vec2(0),normalize(vec2(0)) 在 GLSL 里是 undefined,部分 GPU 会产生 NaN 坏点。虽然触发条件很窄(对称形状中心梯度≈0 会被外层 1e-6 阈值跳过),但建议拦截一下:normalize 前先判断 mixedDir 长度,如 float l = length(mixedDir); mixedDir = (l > 0.000001) ? mixedDir / l : gradientDir; 或直接跳过折射。

* @param lightAngle The direction of the light source in degrees, range [-179, 180].
* @param lightIntensity The brightness of edge highlights, range [0, 100].
*/
static std::shared_ptr<GlassStyle> Make(float refraction = 80.0f, float depth = 20.0f,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里参数默认值建议不设置,除非参数通常不修改

Comment thread src/gpu/processors/FragmentProcessor.h Outdated
* @param args The TPArgs used to create the texture proxy.
* @return The locked texture proxy, or nullptr on failure.
*/
static std::shared_ptr<TextureProxy> LockTextureProxy(const Image* image, const TPArgs& args);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fp不是拿来做LockTextureProxy的。你自己创建textureProxy,然后把fp fill上去。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果是拿image的,你也可以直接makeTextureImage,然后拿对应的texture

@@ -0,0 +1,54 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个也挪layer那边

Comment thread src/layers/filters/TentBlurFilter.h Outdated
@@ -0,0 +1,105 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个没人用呀,不需要这个类

Comment thread src/layers/filters/TentBlurFilter.h Outdated
@@ -0,0 +1,105 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TentBlurFilter(LayerImageFilter 包装层)在全仓库无任何使用方,TentBlurImageFilter 也只有 GlassStyle 生成 UDF 一个消费点,且其 lockTextureProxy 约 170 行处理的通用 ImageFilter 场景(drawScale 降采样、radius>64 钳制、needExtraTransform 重缩放)在 GlassStyle 固定调用约束下(源为刚缩放的位图、clipRect=全图、drawScale=1、radius<=60)全部不可达。这两个类是照 GaussianBlur 双层结构抄来的完整脚手架,建议都删掉,把双 pass tent blur 收进 GlassStyle 内部 helper:直接创建两个固定 RGBA8 的 RenderTargetProxy,用 TentBlur1DFragmentProcessor 做 Horizontal/Vertical 两次 fillRTWithFP 即可(约 30 行)。算法本体 TentBlur1DFragmentProcessor + GLSL 保留。顺带还能固定 RT 格式为 RGBA8,消掉 isAlphaOnly 跟随源导致打包失效的隐患。

Comment thread src/layers/layerstyles/GlassStyle.cpp Outdated
imageShader = imageShader->makeWithMatrix(imageMatrix);
paint.setShader(imageShader);

// Keep imageShader on paint so the refraction filter is inlined as a fragment processor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段注释描述的是旧的内联流程,已与现状相反:当前实现是先 makeWithFilter 把折射预光栅化成 refractedImage,再包进 imageShader——shader 上已经没有 filter 了,不存在“inlined as a fragment processor”,也没有 drawImageRect 的选择问题。建议删掉或改写为描述当前流程(预光栅化到低分辨率背景,再经 content alpha mask 绘制)。

@@ -0,0 +1,48 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imageFilter建议单独定在一个文件夹里,不要和filters放一块

// mask blur filter is cached for reuse.
// UDF is capped at MAX_UDF_SIZE to prevent excessive texture allocation. The mask UVs
// are normalized (0-1), so lower resolution does not affect the refraction shader.
static constexpr float MAX_UDF_SIZE = 512.0f;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可选优化(供作者评估,不要求本期处理):UDF 是被 tent blur 抹平的低频高度场,满足 scale 协变——可以参考高斯模糊 drawScale 的思路把 Fine UDF 从 512 降到 256(半径同步减半),shader 端本来就是归一化 UV + udfPixelToLayerPixel 换算,双线性上采样的误差对梯度计算几乎无感,UDF 链路成本可降到 1/4。也可以按 depth 动态调整:半径越大降得越多。

Comment thread src/layers/layerstyles/GlassStyle.cpp Outdated
// Scale dispersion to [0, 0.2]: the shader offsets R/B UVs by uvOffset * (1 ± dispersion),
// so 0.2 means max 20% additional offset, keeping chromatic aberration subtle.
params.dispersion = (_dispersion / 100.0f) * 0.2f;
params.splay = _splay / 100.0f;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splay 没有在使用点 clamp:文档范围 [0,100],但 _splay 超出时 shader 里 mix(gradientDir, centerDir, splay) 变成外推,产生数学上无意义的折射方向(不是效果强弱问题)。同文件的 refraction/depth 都在使用点 clamp 了,这里建议保持一致:params.splay = std::clamp(_splay / 100.0f, 0.0f, 1.0f);

void setDispersion(float value);

/**
* The spread of projected light across the glass surface. Range [0, 100].

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splay 的文档与当前实现不符(Make 的 @param 同问题):实现里 splay 是折射方向在 UDF 梯度方向与向心方向之间的混合因子(mix(gradientDir, centerDir, splay)),改变的是方向场,并不控制高光的宽度/扩散。这段描述疑似旧解析路径时期留下的,建议改为类似:The blend factor between the UDF gradient direction and the radial (center-pointing) direction used for refraction. Range [0, 100].

* GlassStyle simulates the physical behavior of light passing through a glass surface, producing
* refraction, chromatic dispersion, frosted blur, and specular highlights. It captures the
* background content below the layer and renders it with optical distortion driven by a
* displacement map derived from the layer content's alpha mask.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

公开 API 注释应面向调用方:displacement map derived from the layer content's alpha mask 暴露了内部实现机制(位移图/alpha mask),用户不需要知道。建议只描述行为和用法,例如:It captures the background content below the layer and renders it with optical distortion shaped by the layer's content.

void setDispersion(float value);

/**
* The blend factor between the UDF gradient direction and the radial (center-pointing)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UDF 是内部实现术语,公开注释里用户无从理解(@param splay 一处同问题)。建议改成视觉效果描述,例如:The blend factor for the refraction direction. Range [0, 100]. At 0, refraction follows the curvature of the shape's edges; at 100, it points toward the shape center.

/** Sets the refraction direction blend factor. */
void setSplay(float value);

/** Light source direction in degrees. Range [-179, 180]. */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

角度缺少基准约定,用户无法预测光照方向。按当前实现(lightDir = (sin, cos),+Y 向下):0° 是正上方光源,正值顺时针旋转(90° 光源在左侧)。建议把约定写进注释,例如:Light source direction in degrees. 0 means light from directly above, positive values rotate clockwise. Range [-179, 180].

@Hparty
Hparty merged commit fe7ca97 into main Jul 23, 2026
14 checks passed
@Hparty
Hparty deleted the feature/fengweizou_glassstyle branch July 23, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants