首页 > 解决方案 > 金属中的 Alpha 混合没有给出预期的结果

问题描述

我在 Metal 中渲染半透明精灵时遇到问题。我已经阅读了这个问题这个问题这个问题,这个问题,还有苹果论坛上的这个帖子,还有更多,但不能完全让它工作,所以请在将此问题标记为重复之前继续阅读

我的参考纹理有四行四列。这些行分别是完全饱和的红色、绿色、蓝色和黑色。这些列的不透明度从 100% 不透明到 25% 不透明(依次为 1、0.75、0.5、0.25 alpha)。

Pixelmator(我创建它的地方)上,它看起来像这样:

在此处输入图像描述

如果我在导出之前插入一个完全不透明的白色背景,它将如下所示:

在此处输入图像描述

...但是,当我将其纹理映射到 Metal 中的四边形上,并在将背景清除为不透明的白色(255、255、255、255)后将其渲染,我得到了:

在此处输入图像描述

......这显然比不透明片段中的应该更暗(后面的亮白色应该“渗透”)

实施细节

我将 png 文件作为我应用程序资产目录中的纹理资产导入 Xcode,并在运行时使用MTKTextureLoader. 该.SRGB选项似乎没有什么不同。

据我所知,着色器代码并没有做任何花哨的事情,但仅供参考:

#include <metal_stdlib>
using namespace metal;

struct Constants {
    float4x4 modelViewProjection;
};

struct VertexIn {
    float4 position  [[ attribute(0) ]];
    float2 texCoords [[ attribute(1) ]];
};

struct VertexOut {
    float4 position [[position]];
    float2 texCoords;
};

vertex VertexOut sprite_vertex_transform(device VertexIn *vertices [[buffer(0)]],
                                         constant Constants &uniforms [[buffer(1)]],
                                         uint vertexId [[vertex_id]]) {

    float4 modelPosition = vertices[vertexId].position;
    VertexOut out;

    out.position = uniforms.modelViewProjection * modelPosition;
    out.texCoords = vertices[vertexId].texCoords;

    return out;
}

fragment float4 sprite_fragment_textured(VertexOut fragmentIn [[stage_in]],
                                         texture2d<float, access::sample> tex2d [[texture(0)]],
                                         constant Constants &uniforms [[buffer(1)]],
                                         sampler sampler2d [[sampler(0)]]) {

    float4 surfaceColor = tex2d.sample(sampler2d, fragmentIn.texCoords);

    return surfaceColor;
}

在应用程序方面,我在渲染通道描述符上使用以下(相当标准的)混合因子和操作:

descriptor.colorAttachments[0].rgbBlendOperation = .add
descriptor.colorAttachments[0].alphaBlendOperation = .add

descriptor.colorAttachments[0].sourceRGBBlendFactor = .one
descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha

descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha

(我尝试将sourceRGBBlendFactorfrom更改.one.sourceAlpha使其更暗。)


如果我在红色背景 (255, 0, 0, 255)上渲染图像,我会得到:

在此处输入图像描述

注意顶行是如何向右逐渐变暗的。它应该一直是相同的颜色,因为它混合了具有相同 RGB 分量(255、0、0)的两种颜色。

我已将我的应用程序剥离到最低限度,并在 Github 上放置了一个演示项目;完整的 Metal 设置可以在存储库的源代码中看到。也许有一些我没有提到的东西导致了这种情况,但不能完全弄清楚是什么......


编辑:

正如@KenThomases 在评论中所建议的那样,我将MTKView属性的值colorPixelFormat从默认值更改.bgra8Unormbgra8Unorm_srgb,并将colorSpace属性设置为与 相同view.window?.colorSpace?.cgColorSpace现在,半透明片段看起来不那么暗了,但仍然不是预期的颜色:

在此处输入图像描述

(顶行应该在红色背景下完全“不可见”,从左到右。)


附录

我遇到了Apple 的关于使用 Shader Debugger 的文档,所以我决定看看当我的应用程序绘制精灵的右上角片段之一时片段着色器中会发生什么(应该是完全饱和的红色不透明度为 25%)。

有趣的是,从片段着色器返回的值(然后将应用 alpha 混合,基于颜色缓冲区的当前颜色和混合因子/函数)是[0.314, 0.0, 0.0, 0.596]

在此处输入图像描述

这个 RGBA 值似乎完全不受MTKTextureLoader.Option.SRGBis truefalse不存在的影响。

请注意,红色分量 ( 0.314) 和alpha分量 ( 0.596)不相等,尽管(如果我没记错的话)它们应该是,对于具有预乘 alpha 的完全饱和的红色。

我想这意味着我已经将问题缩小到纹理加载阶段......?

也许我应该放弃方便MTKTextureLoader,弄脏我的手……?

标签: xcodemetalalphablending

解决方案


好吧,事实证明问题确实出在纹理加载阶段,但不在任何我可以调整的代码中(至少如果坚持的话 MTKTextureLoader)。

似乎我需要在 Xcode 中对我的资产目录的 Attributes Inspector 进行一些更改(但至少现在我可以用以下标签标记我最初的问题Xcode:离青铜徽章更近一步!)。

具体来说,我必须将纹理集的解释属性从“颜色”的默认选项更改为“颜色(非预乘)”

在此处输入图像描述

显然,这些资产目录纹理集在设计时考虑了更传统的纹理图像格式,例如 TGA、PNG(根据规范,它是官方非预乘的)。

我以某种方式期望它MTKTextureLoader足够聪明,可以在加载时为我执行此操作。显然,它不是一条可以可靠地从(例如)PNG 文件的元数据/头中读取的信息。


现在,我的参考纹理呈现出它所有的光辉:

在此处输入图像描述

作为最终的更严格的测试,我可以确认所有 4 种颜色都“消失”在等效的 RGB 背景上,无论纹素的不透明度如何:

在此处输入图像描述


推荐阅读