当alpha值不为1或0时,Metal MTLTexture会用黑色替换半透明区域。

18
在使用苹果纹理导入器或其他导入器时,一个在软件中绘制的带有白色柔边圆圈(透明背景)或在Photoshop中保存为PNG格式的图像,当被渲染后,它的半透明颜色在Metal中会被替换为黑色。
下面是从Xcode的Metal调试器中截图,你可以看到发送到着色器之前的纹理。 图片位于此处(我没有足够高的排名来嵌入) 在Xcode、Finder和UIImageView中,源纹理没有圆环。但是在UIImage -> CGContext -> MTLTexture的过程中(我特别想到MTLTexture部分),透明区域会变暗。
我已经改变了所有能改变的东西几天了,但是还是无法解决问题。
为了更加透明,请看一下我的个人导入代码。
import UIKit
import CoreGraphics

class MetalTexture {

    class func imageToTexture(imageNamed: String, device: MTLDevice) -> MTLTexture {
        let bytesPerPixel = 4
        let bitsPerComponent = 8

        var image = UIImage(named: imageNamed)!

        let width = Int(image.size.width)
        let height = Int(image.size.height)
        let bounds = CGRectMake(0, 0, CGFloat(width), CGFloat(height))

        var rowBytes = width * bytesPerPixel
        var colorSpace = CGColorSpaceCreateDeviceRGB()

        let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, rowBytes, colorSpace, CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue))

        CGContextClearRect(context, bounds)
        CGContextTranslateCTM(context, CGFloat(width), CGFloat(height))
        CGContextScaleCTM(context, -1.0, -1.0)
        CGContextDrawImage(context, bounds, image.CGImage)

        var texDescriptor = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.RGBA8Unorm, width: width, height: height, mipmapped: false)

        var texture = device.newTextureWithDescriptor(texDescriptor)
        texture.label = imageNamed

        var pixelsData = CGBitmapContextGetData(context)

        var region = MTLRegionMake2D(0, 0, width, height)
        texture.replaceRegion(region, mipmapLevel: 0, withBytes: pixelsData, bytesPerRow: rowBytes)

        return texture
    }
}

但我认为这不是问题所在(因为它是用Swift复制的苹果代码,而且我已经用他们的代码代替了,没有任何区别)。

任何线索都将非常有帮助。


那实际上不是“黑色”,你看到的是白色,乘以图像的alpha通道两次,再将背景乘以alpha的倒数并加到结果中。我想CGImageAlphaInfo.Last会解决这个问题,但它在硬件上没有实现。你能否实现预乘alpha混合,直到我们找到如何获取非预乘数据的方法? - user652038
1
啊哈!你带我走上了正确的轨道。现在发布我的答案。 - Jake Sawyer
2个回答

10

由于您使用的是配置为使用带 alpha 通道预乘颜色的 CGContext,因此使用标准的 RGB = src.rgb * src.a + dst.rgb * (1-src.a) 将导致暗区域,因为 src.rgb 值已经通过 src.a 预乘。这意味着您想要的是 src.rgb + dst.rgb * (1-src.a),它的配置如下:

pipeline.colorAttachments[0].isBlendingEnabled = true
pipeline.colorAttachments[0].rgbBlendOperation = .add
pipeline.colorAttachments[0].alphaBlendOperation = .add
pipeline.colorAttachments[0].sourceRGBBlendFactor = .one
pipeline.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
pipeline.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
pipeline.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
< p> .one 表示保持 RGB 不变。预乘颜色的原因是您只需要在创建时乘以一次颜色,而不是每次混合时都要进行乘法运算。您的配置也可以实现这一点,但是间接地实现。


7

感谢Jessy,我决定仔细查看我的alpha混合方式,并找到了解决方法。在GPU调试器中,我的纹理仍然看起来变暗了,但在实际应用程序中一切都正常。我对管线状态描述符进行了更改,如下所示。

pipelineStateDescriptor.colorAttachments[0].blendingEnabled = true
pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .Add
pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .Add
pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .DestinationAlpha
pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .DestinationAlpha
pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .OneMinusSourceAlpha
pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .OneMinusBlendAlpha

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