预乘 Alpha 图层合成为什么会越叠越暗?

问题与范围

在 Metal 图像处理里,把一个半透明 PNG、贴纸或效果层叠到背景上看起来很直接:采样前景,选一个 Blend Mode,再写回目标纹理。真正容易出错的地方却在颜色表示。许多纹理使用预乘 Alpha:RGB 已经乘过一次 Alpha;如果混色或覆盖阶段又把这份 RGB 当作直通颜色再乘一次,半透明区域会比应有结果更暗。

这种问题不一定在不透明背景上立刻显眼。它常在多层贴纸、半透明阴影、16-bit 浮点纹理或多次导出后变成边缘发灰、颜色不稳定,甚至只在某个 Blend Mode 中出现。本文讨论单帧 GPU 图层合成的颜色与采样合同,不讨论相机、视频编解码、设备级性能或完整编辑器验收。

先给结论

  • 预乘输入在进入混色公式前需要还原为直通颜色;Alpha 只应在覆盖度合成中参与一次。
  • Blend Mode 解决的是颜色关系,coverage 解决的是前景对结果的影响范围;两者不能混成一条重复乘 Alpha 的计算。
  • hue、saturation、color、luminosity 不是逐通道公式,验证时应与独立的颜色处理参考实现比较。
  • 完整同尺寸图层要按像素中心采样;否则 tile 尺寸或边缘坐标变化可能引入额外重采样。

为什么预乘 RGB 会被算暗

设一个半透明红色前景的预乘像素是 [0.5, 0, 0, 0.5]。其中 0.5 已经是直通红色 1.0 乘以 Alpha 的结果。若 shader 直接把 0.5 当作直通颜色参与混色,再在 coverage 阶段乘 0.5,颜色贡献就变成了 0.25,前景被错误地减弱了一次。

因此,一个清晰的计算顺序是:先从预乘 RGB 与 Alpha 取得直通颜色;在直通颜色上执行 sourceOver、softLight 或其他 Blend Mode;最后把 opacity、mask、圆角等覆盖因素合成为 coverage,并产生符合 Alpha 合同的输出。透明背景同样不能被当作特殊捷径:RGB 与 Alpha 都需要遵守同一表示规则。

这并不是要求所有中间纹理都改成非预乘格式。关键在于明确每一步的表示:采样到的是预乘值,混色使用的是直通颜色,输出重新满足目标纹理所要求的预乘语义。

Blend Mode 的验证不能只看截图

可分离模式可以用独立公式逐通道检查,例如 linear burn、divide、vivid light、linear light、pin light 与 hard mix。端点尤其要单独覆盖:color dodge 的前景接近白色、color burn 的前景接近黑色时,都不能让除法溢出或产生不确定结果。

分量模式则不同。hue、saturation、color 与 luminosity 需要保留或替换颜色的亮度、饱和度等分量,不能用每个 RGB 通道各算一次的方式冒充。可靠测试会同时使用有色与灰度输入,并把结果与独立的 Core Image 参考结果相比较。

同样重要的是覆盖度。opacity 从 1 变为 0.25 应只改变前景的覆盖范围,不能让前景 Alpha 在颜色路径里被第二次折减。对于使用高精度纹理的路径,还应断言输出仍保持输入所需的像素格式,而不是悄悄降到 8-bit。

像素中心也是合成合同的一部分

当图层刚好覆盖完整输出时,每个输出像素都应对应图层中的同一像素。归一化采样坐标使用像素中心:

text 复制代码
(grid + 0.5) / outputSize

若使用网格角点,或把 tile 尺寸变化后的坐标误当作原图坐标,边缘可能落到相邻 texel 的插值区域。单次画面里它看似只是一点模糊;在连续的图层、遮罩和混色之后,则会成为难以复现的颜色差异。验证同尺寸图层时,应直接比较输出像素,而不是只依赖视觉截图。

本机验证与参考实现

Harbeth 的 LayerComposite 是可直接接入 HarbethIO 的图层合成滤镜原语。本轮本机源码提供 sourceOver、softLight、color dodge/burn,以及 linear burn、divide、vivid light、linear light、pin light、hard mix 和四种颜色分量模式。实现把预乘图层还原到直通颜色后混色,再按 coverage 写出结果;完整同尺寸图层使用像素中心坐标采样。

在 Harbeth 329c5146、macOS arm64e 上,本轮执行:

text 复制代码
swift test --filter LayerCompositePremultipliedAlphaTests

共执行 9 项、0 失败,测试耗时 0.438 秒。覆盖范围包括预乘前景不被重复乘 Alpha、透明背景下的颜色与组合 Alpha、opacity coverage、可分离模式的独立公式、与 Core Image 的分量模式比较、dodge/burn 端点、16-bit 浮点输出格式以及同尺寸图层的像素中心对应。它是当前本机的定向单元与像素证据,不代表全设备、HDR 视觉或性能验收已经完成。

Kakapos 在媒体链路中可将 Harbeth 作为逐帧处理后端注入;但时间线、预览、导出与媒体生命周期仍由 Kakapos 负责,图层 Blend Mode 与像素合成语义不由媒体编排层决定。

小结

图层越叠越暗时,先确认纹理是否是预乘表示,再把混色与 coverage 分开检查;随后用独立公式和颜色参考实现验证模式,并让同尺寸图层按像素中心对应。把颜色表示、Alpha、采样坐标都写成可测试的合同,才不会把一个"看似正确"的 Blend Mode 留给下一次导出或多层合成去暴露问题。

你在实现透明图层时,是在哪里定义 Alpha 表示和 coverage 的?欢迎分享遇到过的边缘变暗、颜色漂移或像素对齐问题。

源码与参考实现

本文基于本轮本机源码与定向测试,不代表已经公开发版或完成全设备验证。

相关推荐
蜡台2 小时前
AI Agent 架构终极教程:从原理分层、四大范式到生产级落地实战
网络·人工智能·架构
用户8314550980314 小时前
如一 Agent 架构解读(二):上下文工程——为模型构造它唯一的现实
人工智能·架构
怕浪猫4 小时前
Agent 工程化面试:从开发到部署的 5 个关键问题
面试·架构·github
行者全栈架构师4 小时前
【鸿蒙心迹】鸿蒙网络请求架构实战——@ohos.net.http 到 Axios 封装、拦截器与统一错误处理(HarmonyOS 7.x)
前端·算法·架构
m0_587383005 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
行者全栈架构师5 小时前
从 55% 到 6%:一个快餐营养规划器的算法迭代实录
后端·算法·架构
ZGIAI5 小时前
Agent 重试会不会越帮越乱?
人工智能·架构
用户6919026813395 小时前
LangChain 与 LangGraph:从「链」到「图」,一次讲清它们是什么、什么关系
面试·架构·设计
吴建旭 智宅焕6 小时前
智能家居全国交付知识生产系统的真实性架构:从AI生成内容到可验证交付资产
人工智能·架构·智能家居