我在Metal中渲染半透明精灵时遇到了麻烦。我已经在Apple的论坛上阅读了this question,this question,this one和this thread以及其他几个,但不能完全使用它,所以请在将此问题标记为重复之前继续阅读。
我的参考纹理有四行四列。行分别是完全饱和的红色,绿色,蓝色和黑色。柱的不透明度从100%不透明到25%不透明(依次为1,0.75,0.5,0.25α)。
在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
(我试过将sourceRGBBlendFactor
从.one
更改为.sourceAlpha
使它变得更暗。)
如果我在红色背景(255,0,0,255)上渲染图像,我得到这个:
注意顶行如何逐渐变深。它应该是相同的颜色,因为它混合了具有相同RGB分量(255,0,0)的两种颜色。
我已将我的应用程序剥离到最低限度并放入demo project on Github;可以在存储库的源代码中看到完整的Metal设置。也许我没有提到的是造成这种情况的东西,但不能弄清楚是什么......
编辑:
正如@KenThomases在评论中所建议的那样,我将MTKView
属性colorPixelFormat
的值从.bgra8Unorm
的默认值更改为bgra8Unorm_srgb
,并将colorSpace
属性设置为与view.window?.colorSpace?.cgColorSpace
相同。现在,半透明片段看起来更暗,但仍然不是预期的颜色:
(从左到右,顶行应该对红色背景完全“隐形”。)
附录
我遇到了Apple's docs on using the Shader Debugger,所以我决定看一下片段着色器中发生的事情,当我的应用程序绘制精灵的右上角片段之一时(在25%不透明度下,它是完全饱和的红色)。
有趣的是,从片段着色器返回的值(然后将根据颜色缓冲区的当前颜色和混合因子/函数应用alpha混合)是[0.314, 0.0, 0.0, 0.596]
:
这个RGBA值似乎完全不受MTKTextureLoader.Option.SRGB
是true
,false
还是缺席的影响。
请注意,红色成分(0.314
)和alpha成分(0.596
)不相等,尽管(如果我没有记错的话)它们应该是,对于具有预乘alpha的完全饱和的红色。
我想这意味着我已将问题缩小到纹理加载阶段......?
也许我应该放弃方便的MTKTextureLoader
并弄脏我的手......?
好吧,事实证明问题实际上是在纹理加载阶段,但不是在我可能调整的任何代码片段中(至少如果坚持使用MTKTextureLoader
)。
似乎我需要在Xcode中对我的资产目录的属性检查器进行一些更改(但至少现在我用Xcode
标记我的原始问题:距离青铜徽章更近一步了!)。
具体来说,我不得不将纹理集的Interpretation属性从默认选项“Colors”更改为“Colors(non-premultiplied)”:
Cleary,这些资产目录纹理集在设计时考虑了更传统的纹理图像格式,例如TGA,不是PNG(根据规范,它是官方非预乘的)。
我不知何故预计MTKTextureLoader
会在加载时为我做这件事。显然,不是可以从(例如)PNG文件的元数据/标题中可靠地读取的信息。
现在,我的参考纹理呈现出所有明亮的光彩:
作为最后的,更严格的测试,我可以确认所有4种颜色在等效的RGB背景上“消失”,无论纹素的不透明度如何: