金属中的Alpha混合没有给出预期的结果

问题描述 投票:3回答:1

我在Metal中渲染半透明精灵时遇到了麻烦。我已经在Apple的论坛上阅读了this questionthis questionthis onethis thread以及其他几个,但不能完全使用它,所以请在将此问题标记为重复之前继续阅读。

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

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

enter image description here

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

enter image description here

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

enter image description here

......它显然比非透明碎片中的颜色要暗(背后明亮的白色应该“渗透”)。

Implementation Details

我在我的应用程序的资产目录中将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,并将colorSpace属性设置为与view.window?.colorSpace?.cgColorSpace相同。现在,半透明片段看起来更暗,但仍然不是预期的颜色:

enter image description here

(从左到右,顶行应该对红色背景完全“隐形”。)


附录

我遇到了Apple's docs on using the Shader Debugger,所以我决定看一下片段着色器中发生的事情,当我的应用程序绘制精灵的右上角片段之一时(在25%不透明度下,它是完全饱和的红色)。

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

enter image description here

这个RGBA值似乎完全不受MTKTextureLoader.Option.SRGBtruefalse还是缺席的影响。

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

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

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

xcode metal alphablending
1个回答
5
投票

好吧,事实证明问题实际上是在纹理加载阶段,但不是在我可能调整的任何代码片段中(至少如果坚持使用MTKTextureLoader)。

似乎我需要在Xcode中对我的资产目录的属性检查器进行一些更改(但至少现在我用Xcode标记我的原始问题:距离青铜徽章更近一步了!)。

具体来说,我不得不将纹理集的Interpretation属性从默认选项“Colors”更改为“Colors(non-premultiplied)”:

enter image description here

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

我不知何故预计MTKTextureLoader会在加载时为我做这件事。显然,不是可以从(例如)PNG文件的元数据/标题中可靠地读取的信息。


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

enter image description here

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

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.