What is the setting for classic Porter-Duff blending.

I need to render textures that have transparency and I cannot figure out the correct colorAttachment rgb and alpha blend factors.


Where I'm at:


1) PNG file saved from Photoshop with 50% opacity. No alpha channel added. Just 50% opacity setting in the Levels panel.


2) My texture shader is trivial. Here is the fragment shader:


fragment float4 textureFragmentShader(_Vertex_ vert [[ stage_in ]], texture2d<float> texas [[ texture(0) ]]) {

    constexpr sampler defaultSampler;

    float4 rgba = texas.sample(defaultSampler, vert.st).rgba;
    return rgba;
}


3) This is my colorAttachment setup:

renderToTexturePipelineDescriptor.colorAttachments[ 0 ].isBlendingEnabled = true
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].rgbBlendOperation = .add
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].alphaBlendOperation = .add
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].sourceRGBBlendFactor = .sourceAlpha
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].sourceAlphaBlendFactor = .sourceAlpha
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].destinationRGBBlendFactor = .oneMinusSourceAlpha
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].destinationAlphaBlendFactor = .oneMinusSourceAlpha


4) The rendered result - here a textured rectangle against a white background is clearly wrong:

What are the correct blend factors?


Thanks,

Doug

Accepted Reply

Solved. I had to disable sRGB mode during texture creation.


let textureLoaderOptions:[String:NSNumber] = [ MTKTextureLoaderOptionSRGB:false ]
heroTexture = try textureLoader.newTexture(with: image.cgImage!, options: textureLoaderOptions)

Replies

For some reason the forum has blocked me from uploading the explainatory images. Sigh ...

Your operations and factors are correct for source-over blending. You might be affected by the fact that PNGs are stored without premultiplying, using so-called "unassociated" alpha values, whereas you should be working with premultiplied ("associated") values.


You can quickly validate whether this is the issue by multiplying the RGB components of the sampled color by the sampled alpha. If this improves things, you should ensure that your texture data is stored premultiplied instead of non-premultiplied (i.e., multiply the RGB pixel data by the alpha values when creating the texture, so that sampling, mipmapping, and blending work correctly).

wcm,

I actually forgot a critical piece of info. I import my PNG as an UIImage and then extract it's cgImage - which is pre-multiplied:


        /
        do {
            let textureLoader = MTKTextureLoader(device: device)
            guard let image = UIImage(named:"red_translucent") else {
                fatalError("Error: Can not create UIImage")
            }
            if (image.cgImage?.alphaInfo == .premultipliedLast) {
                print("texture uses premultiplied alpha. Rock.")
            }
        
            heroTexture = try textureLoader.newTexture(with: image.cgImage!, options: nil)
        } catch {
            fatalError("Error: Can not load texture")
        }


So, the print statement "texture uses premultiplied alpha. Rock." does print.


Also, I changed the blend functions to the Porter/Duff "over" operations - A over B = 1 * A + (1 - A.alpha) * B


renderToTexturePipelineDescriptor.colorAttachments[ 0 ].sourceRGBBlendFactor = .one
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].sourceAlphaBlendFactor = .one
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].destinationRGBBlendFactor = .oneMinusSourceAlpha
renderToTexturePipelineDescriptor.colorAttachments[ 0 ].destinationAlphaBlendFactor = .oneMinusSourceAlpha

Specifically,

for a 50% translucent red texture on a quad against a white background the rgb on the quad is (182, 127, 127). The same texture in a layer atop a white layer is correct at (255, 127, 127).


What is attenuating the color in the render?

Out of curiosity have you tried not premultiplying the texture? PNGs are not premultiplied and that would be the case in Photoshop. I wonder if premultiplying it is the reason it looks different. If you know what the RGB values of the original color is without the transparency and then factor in the transparency you can do the math yourself on what the values will be with or without premultiplying, over a white background. Wikipedia and some other websites show how the math works if you search for alpha compositing.

btipling,

So, for completeness, yes PNG with transparency when exported from Photoshop is not pre-multiplied. Agreed. However, when I bring the PNG into my app as a UIImage and use the cgImage field for my texture(s) that is pre-multiplied as confirmed in the snippet of code shown above. The string "texture uses premultiplied alpha. Rock." prints when I load the image and create the texture.


I much prefer pre-mult as it allows all channels to be treated indentically. The over operation - the workhorse compositing operation I intend to use in this and other apps - becomes the trivially simple: SourceColor.rgba + SourceColor.a * DestinationColor.rgba.

If folks want to take a look at the code it is on Github here:

https://github.com/turner/HelloMetal/tree/2_pass_render

Solved. I had to disable sRGB mode during texture creation.


let textureLoaderOptions:[String:NSNumber] = [ MTKTextureLoaderOptionSRGB:false ]
heroTexture = try textureLoader.newTexture(with: image.cgImage!, options: textureLoaderOptions)

I'm glad you solved this, and I learned something, but after reading through sRGB and about sRGB and photoshop I wonder if the better solution would be to enable sRGB in Photoshop instead of turning it off in your Metal project as sRGB standardizes a color space that do what you want, ensure that colors look the same across devices and screens. Maybe not so much of a problem with iOS as it will probably look the same across iOS devices. Not sure. It looks like Photoshop should be configured with sRGB on by default, I wonder also if you disabled this or what the setting is for you.

Yes btipling I agree. I intend to take a closer look a sRGB. Step by step. Cheers.