[DX9] How to correctly render several alpha textures to alpha RT?

Started by
8 comments, last by Igor Kokarev 11 years, 1 month ago

I need to render several alpha textures (PNG images - photos and text) to 32-bit alpha RT. And then render alpha RT to backbuffer. This is for 2D multimedia app.

D3DBLENDOP_ADD doesn not mix correctly layers. The result is not same if I render alpha textures directly to 24-bit backbuffer.

In other words, I need same alpha blending as used for layers in Photoshop (by default).

I know alternate solution with rendering of each alpha texture to RT and then use shaders. But it works slow because of multiple switching of rendertargets for each alpha texture.

Advertisement

You need to set your blend render states correctly. Have a look at http://www.toymaker.info/Games/html/render_states.html under "blend states" and http://msdn.microsoft.com/en-us/library/windows/desktop/bb172508%28v=vs.85%29.aspx. I can't give you the correct settings on top of my head but you should be able to figure it out yourself.


What you need to do is multiply the transparency values together (where transparency is (1.0 - opacity)). 

Start by clearing your render target's alpha to 1.0. Then render your layers onto the render target using these blend modes:

SetRenderState( D3DRS_SEPARATEALPHABLENDENABLE, TRUE );
SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD)
SetRenderState(D3DRS_SRCBLENDALPHA,D3DBLEND_ZERO);
SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);

Your render target should now have correct transparency, but it's the inverse of what you'll be used to (0.0 = opaque, 1.0 = transparent). So when you render it you need to use something like:


SetRenderState( D3DRS_SEPARATEALPHABLENDENABLE, FALSE );
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD)
SetRenderState(D3DRS_SRCBLEND,D3DBLEND_INVSRCALPHA);
SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCALPHA);

Apologies for the wonky formatting.

C0lumbo,
Thanks you for advice!
But if we understood rightly your sample, it doesn't take alpha of source textures. My images has semi-transparent areas and with your code pictures will have aliased edges. Right?
We need the following result:
resultAlpha = dstAlpha + srcAlpha - dstAlpha*srcAlpha ;
resultRed = ( dstRed*dstAlpha*(1-srcAlpha) + srcRed*srcAlpha ) / resAlpha ;
resultGreen = ( dstGreen*dstAlpha*(1-srcAlpha) + srcGreen*srcAlpha ) / resAlpha ;
resultBlue = ( dstBlue*dstAlpha*(1-srcAlpha) + srcBlue*srcAlpha ) / resAlpha ;

Same formala is used in Photoshop to mix layers.

D3DBLENDOP_ADD doesn not mix correctly layers. The result is not same if I render alpha textures directly to 24-bit backbuffer.

To fix that, try using pre-multiplied alpha (your textures need to be processed accordingly though):

http://blogs.msdn.com/b/shawnhar/archive/2009/11/07/premultiplied-alpha-and-image-composition.aspx

For your render states, this is One for your source blends, and inversesourcealpha for your destination blends (and still Add for the operation).

C0lumbo,
Thanks you for advice!
But if we understood rightly your sample, it doesn't take alpha of source textures. My images has semi-transparent areas and with your code pictures will have aliased edges. Right?
We need the following result:
resultAlpha = dstAlpha + srcAlpha - dstAlpha*srcAlpha ;
resultRed = ( dstRed*dstAlpha*(1-srcAlpha) + srcRed*srcAlpha ) / resAlpha ;
resultGreen = ( dstGreen*dstAlpha*(1-srcAlpha) + srcGreen*srcAlpha ) / resAlpha ;
resultBlue = ( dstBlue*dstAlpha*(1-srcAlpha) + srcBlue*srcAlpha ) / resAlpha ;

Same formala is used in Photoshop to mix layers.

Yes, I don't think the code I posted will achieve that formula. I assumed you wanted the equivalent of doing standard alpha blending (resultRGB = sourceRGB*sourceAlpha+destRGB*(1.0 - sourceAlpha)), but with multiple layers on render-to-texture instead of simply rendering directly onto the frame buffer.

phil_t,

Thanks, we'll try idea with premultiplied alpha. Very interesting.

Please look on the following illustration:

[attachment=14384:86704_1363885776_compare.png]

- Left picture - layers in Photoshop.

- Right picture - rendering of alpha textures to alpha rendertarget.

Problems of DirectX rendering with D3DBLENDOP_ADD

1. Shadows of globes are more bright than on left picture.

2. Hardly visible visual artefacts on a border between two globes.

3. Color of shadows of globes depends on color of rendertarget, although color is transparent. Left part - black background color (alpha 0), right part - background color is red (alpha 0).

So as you can see the result is terrible. And we can't mix layers correctly for our multimedia application.

To expand on the pre-multiplied alpha, first consider the regular alpha-blending case:

You have a grey texture at 50% alpha. so: (0.5, 0.5, 0.5, 0.5)

-Now you draw that into a transparent render target.
-The equation is (s * alpha) + (d * invalpha), or (0.5 * 0.5) + (0 * 0.5)
-The result is (0.25, 0.25, 0.25, 0.25)
-Now you take that and draw it into another transparent render target
-Again, the equation is (s * alpha) + (d * invalpha), or (0.25 * 0.25) + (0 * 0.25)
-The result is (0.125, 0.125, 0.125, 0.125). Too dark!
With pre-multiplied:
You have a grey texture at 50% alpha, premultiplied. so: (0.25, 0.25, 0.25, 0.5)
-Now you draw that into a transparent render target.
-The equation is (s * 1) + (d * invalpha), or (0.5 * 1) + (0 * 0.5)
-The result is (0.25, 0.25, 0.25, 0.5) // Same RGB as with regular alpha, but note that alpha is still 0.5!
-Now you take that and draw it into another transparent render target
-Again, the equation is (s * 1) + (d * invalpha), or (0.25 * 1) + (0 * 0.5)
The result is (0.25, 0.25, 0.25, 0.5). // The correct result!

Note that in your last post, filling a render target with transparent red wouldn't really happen in the pre-multiplied alpha world, since premultiplied transparent red is (0, 0, 0, 0).

phil_t,

Thanks again! I'll write about result.

phil_t,

Results are brilliant! Exactly what we need for a correct alpha blending. Thank you!

This topic is closed to new replies.

Advertisement