Rendering to texture somehow disabled blending

I am working on a 2d project with a Vulkan backend, and I had a setup working where I rendered sprites directly to the screen. These sprites had some pixels with an alpha of 0, and as expected, those areas weren’t drawn to the screen.

Now, I decided to switch the rendering model to render first to a fixed size attachment, then render that to the screen attachment in a later RenderPass, using a Combined Image Sampler Descriptor Set and a quad mapped to the four corners. As soon as I did that, the pixels in the sprites texture with alpha 0 started drawing as black pixels (as the actual rgba value there is [0, 0, 0, 0]) OVER the clear value, which it did not do before.

I’m stumped trying to fix this, as I use virtually the same code for creating the pipeline that renders to the new off-screen attachment. To be exact, the blend state is configured like this for both:

auto colorBlendAttachment = VkPipelineColorBlendAttachmentState{
    .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
                      | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
    .blendEnable         = VK_TRUE,
    .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
    .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
    .colorBlendOp        = VK_BLEND_OP_ADD,
    .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
    .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
    .alphaBlendOp        = VK_BLEND_OP_ADD};

auto colorBlending = VkPipelineColorBlendStateCreateInfo{
    .sType             = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
    .logicOpEnable     = VK_FALSE,
    .attachmentCount   = 1,
    .pAttachments      = &colorBlendAttachment
};

The off-screen attachment is the same color format as the screen attachment: VK_FORMAT_B8G8R8A8_UNORM. In the subpass for the first RenderPass, the fixed size attachment goes from initial layout UNDEFINED, to final layout of SHADER_READ_ONLY_OPTIMAL, and is used in subpass where it’s rendered to as COLOR_ATTACHMENT_OPTIMAL, and it’s created with usage COLOR_ATTACHMENT and SAMPLED.

The LoadOp is Clear (to the red value [0.5, 0, 0, 1]) and the StoreOp is Store for drawing to the fixed size attachment.

I tried to see if the shaders even were getting the alpha data, so in the Fragment Shader for the pipeline rendering to the off-screen attachment I changed:
out_color = texture(in_texture, in_uv);
to

vec4 color = texture(in_texture, in_uv);

if (color.a == 0)
    discard;

out_color = color;

and it discarded the otherwise black pixels (which makes sense, as they are [0, 0, 0, 0] in the texture).

I tried a similar route when rendering the off-screen attachment to the screen attachment
out_color = texture(in_texture, in_uv);
to

vec4 color = texture(in_texture, in_uv);

if (color.a == 0)
    color = vec4(0.0, 1.0, 0.0, 1.0);

out_color = color;

and again, it now rendered the black, alpha == 0 pixels as green. So it seems that the alpha values are there, in both stages of rendering, but it’s just not blending.

Thanks for any insight and help, let me know if there’s any other information that is relevant I might have missed or forgotten!

So if the source alpha is 0, discard its RGB and keep the RGB in the render target.

If the source alpha is 0, write alpha = 0 to render target.

What’s the clear color for your screen attachment? Unless I’m missing something, it appears that’ll be the color of the alpha=0 pixels.

Often when you composite alpha objs to a layer, and then render that composited layer to the screen, you want to at least consider using pre-multiplied alpha.

1 Like

The texture is cleared to [0.5, 0, 0, 1] so the source (background) alpha is 1. I would assume then that the color result would be [0.5, 0, 0] * 1 + [0, 0, 0] * (1-1) resulting in the color [0.5, 0, 0], and the alpha would just be (1 + 0) resulting in the combined rgba of [0.5, 0, 0, 1], no? I’m really going for barebones with this demo.

The problem you mentioned was on the alpha=0 sprite pixels, so we should trace that case (e.g. Sprite RGBA = (1,1,1,0).

Pass 1:

  • RGB = Sprite RGB * Sprite A + Offscreen RGB * (1 - Sprite A)
  • RGB = (1,1,1) * (0) + (0.5,0,0) * (1-0)
  • RGB = (0.5,0,0)
  • A = ONE * Sprite A + ZERO * Offscreen A
  • A = Sprite A
  • A = 0

Pass 2:

  • RGB = Offscreen RGB * Offscreen A + Screen RGB * ( 1 - Offscreen A )
  • RGB = (0.5,0,0) * 0 + (0,0,0) * (1-0)
  • RGB = (0,0,0)
  • A = ONE * Offscreen A + ZERO * Screen A
  • A = Offscreen A
  • A = 0

From your prior post, I think you’re indicating that your Screen RGB = (0,0,0), which would explain the results.

1 Like

Wow, yeah that was exactly it. I wasn’t considering the alpha blend factor since before it was “working” and I hadn’t changed it at all.

To confirm I did two things:
First, I swapped the clear color of the screen attachment to blue, and now the previously incorrect black pixels were blue, confirming that the black was actually the screen RGB coming through.

Second, I simply swapped

srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,

to

srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE,

and it drew my original expected output, with no black borders.

Thank you so much! I was definitely tearing my hair out over this.

No problem! Glad we got it figured out.