Search code examples
iosmetalalphablending

Alpha blending with two transparent textures not working correctly


I have a destination texture: enter image description here

Here the whole texture will be transparent (alpha = 0) except red color part. Red color will have alpha value of 0.5. I used a rectangle plane to present this texture.

Then i have this source texture. It is also a transparent texture with black color part. Black color will have alpha value of 0.5. I used another rectangle plane to present this texture and i change MTLRenderPipelineDescriptor blending to

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

Here blending works fine between two textures.

enter image description here

Then i try to merge these two textures into one destination texture. using MTLComputeCommandEncoder. My kernel function:

kernel void compute(
                    texture2d<float, access::read_write> des [[texture(0)]],
                    texture2d<float, access::read> src [[texture(1)]],
                    uint2 gid [[thread_position_in_grid]])
{
    float4 srcColor = src.read(gid);
    float4 desColor = des.read(gid);
    float srcAlpha = srcColor.a;

    float4 outColor = srcColor  + desColor * (1 - srcAlpha);

    des.write(outColor, gid);
}

But after that blended color will be different than previous. Blending color is lighter than previous one. enter image description here

How do I properly blend two transparent textures in kernel function? What is wrong with my solution?


Solution

  • I think that you are using premultiplied alpha...

    Try this instead (which is not premultiplied alpha):

    float4 srcColor = src.read(gid);
    float4 desColor = des.read(gid);
    
    float4 outColor;
    outColor.a = srcColor.a + desColor.a * (1f - srcColor.a);
    if (outColor.a == 0f) {
      outColor.r = 0f;
      outColor.g = 0f;
      outColor.b = 0f;
    } else {
      outColor.r = (srcColor.r * srcColor.a + desColor.r * desColor.a * (1f - srcColor.a)) / outColor.a;
      outColor.g = (srcColor.g * srcColor.a + desColor.g * desColor.a * (1f - srcColor.a)) / outColor.a;
      outColor.b = (srcColor.b * srcColor.a + desColor.b * desColor.a * (1f - srcColor.a)) / outColor.a;
    }