金属中的阿尔法混合没有给出预期的结果
Alpha Blending in Metal Not Giving The Expected Results
我在 Metal 中渲染半透明精灵时遇到问题。我已经在 Apple 的论坛上阅读了 this question, and , and this one, and this thread 以及更多其他内容,但无法完全使用它,因此 请继续阅读,然后再将此问题标记为重复问题。
我的参考纹理有四行四列。这些行分别是完全饱和的红色、绿色、蓝色和黑色。列的不透明度从 100% 不透明到 25% 不透明(1、0.75、0.5、0.25 alpha,按此顺序)。
在 Pixelmator(我创建它的地方)上,它看起来像这样:
如果我在导出之前插入一个完全不透明的白色背景,它将看起来像这样:
...但是,当我将它纹理映射到 Metal 中的四边形上,并在将背景清除为不透明白色后渲染它(255、255、255、255 ), 我明白了:
...这显然 比非不透明片段中的颜色要深 (后面的亮白色应该 "bleed through")。
实施细节
我将 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
相同。 现在,半透明片段看起来不那么暗了,但仍然不是预期的颜色:
(顶行应该完全'invisible'在红色背景下,从左到右。)
附录
我遇到了 Apple's docs on using the Shader Debugger,所以我决定看看当我的应用程序绘制精灵的右上角片段之一时片段着色器中发生了什么(应该是完全-饱和度为 25% 的红色)。
有趣的是,从片段着色器返回的值(然后将应用 alpha 混合,基于颜色缓冲区的当前颜色和混合 factors/functions)是 [0.314, 0.0, 0.0, 0.596]
:
这个RGBA值似乎完全不受MTKTextureLoader.Option.SRGB
是true
、false
还是不存在.[=39=的影响]
注意 red 分量 (0.314
) 和 alpha 分量 (0.596
) 是 不等于,尽管(如果我没记错的话)它们应该是,对于具有预乘 alpha 的完全饱和红色。
我想这意味着我已经将我的问题缩小到纹理加载阶段...?
也许我应该放弃方便的 MTKTextureLoader
并亲自动手......?
好吧,事实证明问题确实出在纹理加载阶段,但不是在我可以调整的任何代码段中(至少如果坚持 MTKTextureLoader
则不会)。
看来我需要在 Xcode 中对资产目录的属性检查器进行一些更改(但至少现在我可以用 Xcode
标记我的原始问题:更近一步到青铜徽章!)。
具体来说,我必须从 [=49= 的默认选项更改纹理集的 Interpretation 属性] 到 "Colors (non-premultiplied)":
很明显,这些资产目录纹理集在设计时考虑了更传统的纹理图像格式,例如TGA,not PNG(根据规范,官方未预乘)。
我以某种方式期望 MTKTextureLoader
足够聪明,可以在加载时为我做这件事。显然,这不是一条可以从(例如)PNG 文件的 metadata/header.
中可靠读取的信息。
现在, 我的参考纹理渲染得光彩夺目:
作为最终的、更严格的测试,我可以确认所有 4 种颜色 "disappear" 在等效的 RGB 背景上,无论纹素的不透明度如何:
我在 Metal 中渲染半透明精灵时遇到问题。我已经在 Apple 的论坛上阅读了 this question, and
我的参考纹理有四行四列。这些行分别是完全饱和的红色、绿色、蓝色和黑色。列的不透明度从 100% 不透明到 25% 不透明(1、0.75、0.5、0.25 alpha,按此顺序)。
在 Pixelmator(我创建它的地方)上,它看起来像这样:
如果我在导出之前插入一个完全不透明的白色背景,它将看起来像这样:
...但是,当我将它纹理映射到 Metal 中的四边形上,并在将背景清除为不透明白色后渲染它(255、255、255、255 ), 我明白了:
...这显然 比非不透明片段中的颜色要深 (后面的亮白色应该 "bleed through")。
实施细节
我将 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
相同。 现在,半透明片段看起来不那么暗了,但仍然不是预期的颜色:
(顶行应该完全'invisible'在红色背景下,从左到右。)
附录
我遇到了 Apple's docs on using the Shader Debugger,所以我决定看看当我的应用程序绘制精灵的右上角片段之一时片段着色器中发生了什么(应该是完全-饱和度为 25% 的红色)。
有趣的是,从片段着色器返回的值(然后将应用 alpha 混合,基于颜色缓冲区的当前颜色和混合 factors/functions)是 [0.314, 0.0, 0.0, 0.596]
:
这个RGBA值似乎完全不受MTKTextureLoader.Option.SRGB
是true
、false
还是不存在.[=39=的影响]
注意 red 分量 (0.314
) 和 alpha 分量 (0.596
) 是 不等于,尽管(如果我没记错的话)它们应该是,对于具有预乘 alpha 的完全饱和红色。
我想这意味着我已经将我的问题缩小到纹理加载阶段...?
也许我应该放弃方便的 MTKTextureLoader
并亲自动手......?
好吧,事实证明问题确实出在纹理加载阶段,但不是在我可以调整的任何代码段中(至少如果坚持 MTKTextureLoader
则不会)。
看来我需要在 Xcode 中对资产目录的属性检查器进行一些更改(但至少现在我可以用 Xcode
标记我的原始问题:更近一步到青铜徽章!)。
具体来说,我必须从 [=49= 的默认选项更改纹理集的 Interpretation 属性] 到 "Colors (non-premultiplied)":
很明显,这些资产目录纹理集在设计时考虑了更传统的纹理图像格式,例如TGA,not PNG(根据规范,官方未预乘)。
我以某种方式期望 MTKTextureLoader
足够聪明,可以在加载时为我做这件事。显然,这不是一条可以从(例如)PNG 文件的 metadata/header.
现在, 我的参考纹理渲染得光彩夺目:
作为最终的、更严格的测试,我可以确认所有 4 种颜色 "disappear" 在等效的 RGB 背景上,无论纹素的不透明度如何: