问题与范围
在 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 的?欢迎分享遇到过的边缘变暗、颜色漂移或像素对齐问题。
源码与参考实现
本文基于本轮本机源码与定向测试,不代表已经公开发版或完成全设备验证。