金属中的Alpha混合未得到预期结果

13
我在Metal中渲染半透明精灵的时候遇到了问题。我已经阅读了这个问题, 这个问题, 这个问题和苹果论坛上的这个主题以及其他一些内容,但仍然无法使其正常工作,所以在将此问题标记为重复之前,请继续阅读
我的参考纹理有四行四列。行分别是完全饱和的红色、绿色、蓝色和黑色。列的不透明度从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)的两种颜色。
我已经将我的应用程序精简到最少,并在Github上放了一个演示项目;完整的Metal设置可以在存储库的源代码中看到。也许有些我没有提到的问题导致了这个问题,但我无法完全弄清楚是什么原因...

编辑:

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

enter image description here

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


附录

我看到了苹果公司关于使用着色器调试器的文档,所以我决定查看我的应用程序在绘制精灵的右上角片段时片段着色器中发生了什么(这个片段应该是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,自己动手处理...?


1
将视图的 colorPixelFormat 设置为 .bgra8Unorm_srgb 有帮助吗?将视图的 colorspace 设置为 view.window.colorSpace.cgColorSpace 有帮助吗? - Ken Thomases
@KenThomases 我将颜色附件的像素格式设置为从 MTKView 获取的值,调试器报告为 .bgra8Unorm。强制将其设置为 .bgra8Unorm_srgb 会在 setRenderPipelineState() 上导致崩溃。 - Nicolas Miari
@KenThomases 等等,我可以将它设置为视图,然后让它传播到颜色附件。但似乎只有一点改善。 - Nicolas Miari
1个回答

14

事实证明问题确实出在纹理加载阶段,但并不在于我可以调整的任何代码片段(至少不是如果坚持使用 MTKTextureLoader)。

看起来我需要在Xcode的资源目录中的属性检查器中引入一些更改(但是至少现在我可以使用 Xcode 标记我的原始问题:离铜徽章又近了一步!)。

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

enter image description here

显然,这些资源目录纹理集是针对更传统的纹理图像格式设计的,例如TGA,而不是PNG(根据规范,PNG官方上是非预乘的)。

我某种程度上期望 MTKTextureLoader 能够在加载时自动智能处理此操作。显然,这并不是可以可靠读取(例如)PNG文件元数据/头的信息。


现在,我的参考纹理以其光辉的荣耀呈现:

enter image description here

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

输入图像描述


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接