Strange alphachannel blending when painting to a DirectX Rendertarget

Started by
10 comments, last by Juliean 1 month, 2 weeks ago

Hello,

I am painting a fully opaque texture and then a semitransparent texture to a DirectX Rendertarget of type R8G8B8A8_UNORM.

However it seems that the alpha channel is not blended correctly even though I am using the blendfunc:
Src: SourceAlpha - Dest: InverseSourceAlpha

It seems as if the texture that gets painted last doesnt even care what is already in the Dest buffer of the RenderTarget. It just overwrites any existing alpha value. Debugging the rendertarget's alphachannel with RenderDoc looks like this:

However since the background texture is fully opaque, I expect also the whole Rendertarget Image to be fully opaque. I.e. the alpha channel should be fully white.

If I paint the same textures to the Swapchain Backbuffer the alphachannel gets blended normally.

Advertisement

What you are seeing is behaving correctly, according to your settings. The way that you describe your blend-setup, is how regular alpha-blending is done. However, in that process, we generally only modify the “Src” and “Dest” parameters, which only affect the color-channels. There are separate blend-settings for the actual alpha-channel. Those by default are

AlphaSrc: One
AlphaDest: Zero

So indeed, by those defaults, the alpha-channel will contain whatever gets written last. This is because for a general use-case, render-targets don't have or need a dedicated alpha-channel (if you do ie offscreen-deferred rendering, you render the whole scene and don't need any cutouts). If you actually really need to have the alpha-channel behave correctly, because you are compositing it onto some other target afterwards, you need to set the aformentioned properties, to the same settings that are used for blending the colors. If not, you'd have to describe the actual bug so we can see what is going wrong, actually.

EDIT: Why it works on backbuffer is probably just a difference in what color it's being cleared to

hi Juliean, thanks a lot for your detailed answer!

So I have a 2D game where a certain element consisting of several images is getting painted to the scene many times. In order to gain performance my idea is to paint these images to a rendertarget once and then paint that rendertarget as a texture many times to the scene. Everything is painted back to front so no DepthTest is needed.

I have already tried to play around with the AlphaSrc and AlphaDest values, but it seems they have no influence at all on the outcome. Even if I use Zero, Zero for example, the outcome on the RenderTargetImage is always the same.

Here is the full BlendState in C#/SharpDX code:

var blendDescription = BlendStateDescription.Default();
blendDescription.IndependentBlendEnable = false;
blendDescription.AlphaToCoverageEnable = false;
for (int i = 0; i < blendDescription.RenderTarget.Length; i++)
{
  blendDescription.RenderTarget[i].IsBlendEnabled = true;
  blendDescription.RenderTarget[i].SourceBlend = BlendOption.SourceAlpha;
  blendDescription.RenderTarget[i].DestinationBlend = BlendOption.InverseSourceAlpha;
  blendDescription.RenderTarget[i].BlendOperation = BlendOperation.Add;
  blendDescription.RenderTarget[i].SourceAlphaBlend = BlendOption.SourceAlpha;
  blendDescription.RenderTarget[i].DestinationAlphaBlend = BlendOption.InverseSourceAlpha;
  blendDescription.RenderTarget[i].AlphaBlendOperation = BlendOperation.Add;
  blendDescription.RenderTarget[i].RenderTargetWriteMask = ColorWriteMaskFlags.All;
}

triangle_eggbeater669 said:
So I have a 2D game where a certain element consisting of several images is getting painted to the scene many times. In order to gain performance my idea is to paint these images to a rendertarget once and then paint that rendertarget as a texture many times to the scene. Everything is painted back to front so no DepthTest is needed.

Seems valid. Not sure if it is really needed or worth the effort - you can render many many 2d images on modern hardware without any issues, but let's just say it is for now.

triangle_eggbeater669 said:
I have already tried to play around with the AlphaSrc and AlphaDest values, but it seems they have no influence at all on the outcome. Even if I use Zero, Zero for example, the outcome on the RenderTargetImage is always the same. Here is the full BlendState in C#/SharpDX code:

Hm, seems correct actually. Can't really say what's wrong here like that. My only advice would be to fire up a graphics-debugger, and check the state of the draw-calls. Visual Studio has one such tool. With that, you can capture a frames rendering, and inspect all the draw-calls. You can look at what the bound states are, and even debug the shaders. Maybe you can spot an issue there. Occam's razor would tell me that probably something of the state-setup is not properly applied, because the settings you have shown in the code would make sense.

Thanks again for your quick answer!

Yeah I was already debugging the Drawcalls with RenderDoc, and to me it seems the correct values are taken, when the texture is painted into the rendertarget.

Strange. Have to you tried to see what happens when you clear the render-target to 0.0f alpha (which you'd need to do anyway), and blend another sprite onto it? Or is the 1.0f-alpha background on the render-target due to a full solid sprite being rendered? Probably doesn't make a difference, since you say thaT changing SrcBlendAlpha/DestBlendAlpha doesn't have any effect, but still worth a shot.

I'll try it out for myself later and let you know if results are similar for me. Maybe I'll notice something that way.

Thanks, yep i played around with various clear values already, but without any change. Normally I am clearing to 0,0,0,0.

But with different values I can make the problem more obvious. For example if I clear to 255,0,0,255 I get opaque red where nothing is painted, but on those pixels where something is painted it just replaces the values of all 4 channels with the input from the texture, as if no blending function would be executed at all.

Oh, am I understanding it correctly, that even the color-channels are not properly blended? I assumed the issue was with the alpha-channel specifically. I'd normally assume that it's something with the alpha that is output from the shader, but since you said that even ZERO has no effect; plus that it works on the backbuffer… you also seem to be already setting all 8 blend-descriptions, so it can't be a mismatching render-target slot, unlikely as that is in the first place.

In that case, me testing it out makes no sense, as my own blending works in a render-target (I just didn't do alpha-blending specifically). Can you perhaps upload the RenderDoc-file somewhere, so we could have a look at it, I'd pretty much need to look through it to be able to help further.

Yep, when I look at the resulting rendertarget in RenderDoc it always looks the same, no matter which values I use for SrcBlend, DestBlend, AlphaSrcBlend, AlphaDestBlend.

I should have mentioned this earlier, but I also noticed this just now because I never paid attention to the RGB values as only the A value looked wrong to me.

It paints as if it uses some default blendstate. Probably the One,Zero that you mentioned in the beginning.

I can prepare a minimal sample and a renderdoc capture that reproduce the problem, but this will take a bit.

While preparing the demo i actually could solve the issue. Here is what I came up with:

Left:
Painted directly to backbuffer with
SourceBlend = SourceAlpha
DestinationBlend = InverseSourceAlpha
SourceAlphaBlend = SourceAlpha
DestinationAlphaBlend = InverseSourceAlpha

Middle:
Painted to transparent RenderTarget with
SourceBlend = SourceAlpha
DestinationBlend = InverseSourceAlpha
SourceAlphaBlend = SourceAlpha
DestinationAlphaBlend = InverseSourceAlpha

Right:
Painted to transparent RenderTarget with
SourceBlend = SourceAlpha
DestinationBlend = InverseSourceAlpha
SourceAlphaBlend = One
DestinationAlphaBlend = InverseSourceAlpha

Sidenote: When painting the RenderTarget itself to the backbuffer I had to use
SourceBlend = One
DestinationBlend = InverseSourceAlpha
SourceAlphaBlend = One
DestinationAlphaBlend = InverseSourceAlpha
otherwise transparent pixels would get blended twice.

So issue is solved, but I dont really understand why you have to use different BlendStates for painting to the Backbuffer and to a RenderTarget, but I guess this comment:

EDIT: Why it works on backbuffer is probably just a difference in what color it's being cleared to

might explain it 🙂

This topic is closed to new replies.

Advertisement