xcode - 金属中的 Alpha 混合没有给出预期的结果
问题描述
我在 Metal 中渲染半透明精灵时遇到问题。我已经阅读了这个问题,这个问题,这个问题,这个问题,还有苹果论坛上的这个帖子,还有更多,但不能完全让它工作,所以请在将此问题标记为重复之前继续阅读。
我的参考纹理有四行四列。这些行分别是完全饱和的红色、绿色、蓝色和黑色。这些列的不透明度从 100% 不透明到 25% 不透明(依次为 1、0.75、0.5、0.25 alpha)。
在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
from更改.one
为.sourceAlpha
使其更暗。)
如果我在红色背景 (255, 0, 0, 255)上渲染图像,我会得到:
注意顶行是如何向右逐渐变暗的。它应该一直是相同的颜色,因为它混合了具有相同 RGB 分量(255、0、0)的两种颜色。
我已将我的应用程序剥离到最低限度,并在 Github 上放置了一个演示项目;完整的 Metal 设置可以在存储库的源代码中看到。也许有一些我没有提到的东西导致了这种情况,但不能完全弄清楚是什么......
编辑:
正如@KenThomases 在评论中所建议的那样,我将MTKView
属性的值colorPixelFormat
从默认值更改.bgra8Unorm
为bgra8Unorm_srgb
,并将colorSpace
属性设置为与 相同view.window?.colorSpace?.cgColorSpace
。现在,半透明片段看起来不那么暗了,但仍然不是预期的颜色:
(顶行应该在红色背景下完全“不可见”,从左到右。)
附录
我遇到了Apple 的关于使用 Shader Debugger 的文档,所以我决定看看当我的应用程序绘制精灵的右上角片段之一时片段着色器中会发生什么(应该是完全饱和的红色不透明度为 25%)。
有趣的是,从片段着色器返回的值(然后将应用 alpha 混合,基于颜色缓冲区的当前颜色和混合因子/函数)是[0.314, 0.0, 0.0, 0.596]
:
这个 RGBA 值似乎完全不受MTKTextureLoader.Option.SRGB
is true
、false
或不存在的影响。
请注意,红色分量 ( 0.314
) 和alpha分量 ( 0.596
)不相等,尽管(如果我没记错的话)它们应该是,对于具有预乘 alpha 的完全饱和的红色。
我想这意味着我已经将问题缩小到纹理加载阶段......?
也许我应该放弃方便MTKTextureLoader
,弄脏我的手……?
解决方案
好吧,事实证明问题确实出在纹理加载阶段,但不在任何我可以调整的代码中(至少如果坚持的话 MTKTextureLoader
)。
似乎我需要在 Xcode 中对我的资产目录的 Attributes Inspector 进行一些更改(但至少现在我可以用以下标签标记我最初的问题Xcode
:离青铜徽章更近一步!)。
具体来说,我必须将纹理集的解释属性从“颜色”的默认选项更改为“颜色(非预乘)”:
显然,这些资产目录纹理集在设计时考虑了更传统的纹理图像格式,例如 TGA、非PNG(根据规范,它是官方非预乘的)。
我以某种方式期望它MTKTextureLoader
足够聪明,可以在加载时为我执行此操作。显然,它不是一条可以可靠地从(例如)PNG 文件的元数据/头中读取的信息。
现在,我的参考纹理呈现出它所有的光辉:
作为最终的更严格的测试,我可以确认所有 4 种颜色都“消失”在等效的 RGB 背景上,无论纹素的不透明度如何:
推荐阅读
- java - 如何在Java中水平打印移动平均线的内容
- c - 如何删除整个列表,而不仅仅是删除列表中的元素?
- css - 标题周围的有限宽度线
- python - 从 Pythom 发送 UTF-8 格式的 QDataStream
- python-3.x - 从另一个 python 文件导入一个函数会导致整个 python 文件被执行,而不仅仅是调用的函数
- javascript - 无法使用 Axios 以 React 形式将数据发布到 NodeJS
- apache - Xampp 本地主机上的 SSL 在 Windows 上使用 VirtualHost 重定向到 Xampp 默认信息/本地主机页面
- c# - 使用 RestSharp 和 oAuth1.0a 将状态发布到 Twitter 时未经授权的访问
- php - PHP - 在电子邮件中插入换行符
- linux - 时间戳到日期的格式转换