金属中的Alpha混合未达到预期的效果

时间:2019-04-10 03:13:20

标签: xcode metal alphablending

我无法在Metal中渲染半透明精灵。我已经在Apple的论坛上阅读了this questionthis questionthis onethis thread,还有其他几个论坛,但不能完全正常工作,所以请先仔细阅读,然后再将此问题标记为重复

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

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

enter image description here

如果我在导出之前插入完全不透明的白色背景,它将看起来像这样:

enter image description here

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

enter image description here

...显然比不透明的片段要暗(更暗)(后面的亮白色应“渗出”)。

实施细节

我将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

(我尝试将sourceRGBBlendFactor.one更改为.sourceAlpha,使其颜色变暗。)


如果我改为在红色背景(255、0、0、255)上渲染图像,则会得到以下提示:

enter image description here

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

我已将我的应用剥离到最低限度,并放置了demo project on Github;完整的Metal设置可以在存储库的源代码中看到。也许有一些我没有提到的原因导致了这种情况,但是我无法完全弄清楚是什么原因。


编辑:

如@KenThomases在评论中所建议,我将MTKView属性colorPixelFormat的值从默认的.bgra8Unorm更改为bgra8Unorm_srgb,并设置了{{ 1}}属性与colorSpace相同。 现在,半透明的片段看起来暗淡得多,但仍然不是预期的颜色:

enter image description here

(第一行应该在红色背景上从左到右完全“不可见”。)


附录

我碰到了Apple's docs on using the Shader Debugger,所以我决定看一下我的应用绘制精灵右上角的片段之一时片段着色器中发生了什么(该片段应该是完全饱和的)透明度为25%时呈红色)。

有趣的是,片段着色器返回的值(然后根据颜色缓冲区的当前颜色和混合因子/函数将其应用于alpha混合)为view.window?.colorSpace?.cgColorSpace

enter image description here

此RGBA值似乎不受[0.314, 0.0, 0.0, 0.596]MTKTextureLoader.Option.SRGBtrue缺席的影响。

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

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

也许我应该放弃方便的0.596并弄脏我的手...?

1 个答案:

答案 0 :(得分:5)

好吧,事实证明问题确实在纹理加载阶段,但是没有我可能需要修改的任何代码(至少如果坚持使用MTKTextureLoader则没有)。

似乎我需要在Xcode中对资产目录的Attributes Inspector进行一些更改(但至少现在我用Xcode标记了我的原始问题:离铜牌更近了一步!) 。

特别是,我不得不将纹理集的 Interpretation 属性从默认选项“颜色” 更改为“颜色(非预乘)”

enter image description here

Cleary,这些资产目录纹理集在设计时会考虑更多传统纹理图像格式,例如TGA, not PNG(根据规范,正式未预乘)。

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


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

enter image description here

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

enter image description here