# Replacing 4 additive sprite layers with a single shader. Just can't get it right

I'm using directx9 and have 4 textures I want to draw on top of each other. if I do this:

PDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); PDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

SetTexture(0,texture1); DrawSprite();

SetTexture(0,texture2); DrawSprite();

SetTexture(0,texture3); DrawSprite();

SetTexture(0,texture4); DrawSprite();

I Get EXACTLY the cool effect i want. but I suspect this level of blatant overdraw is inefficient. I'm sure this can be compressed into a single draw call somehow, and run faster. The trouble is, everything I try does not look the same, whether I try a fixed-function muyltitexturing approach setting the 4 textures as different texture stages, or whether I try to implement it inside an fx file as a shader. Nothing has the same effect.

Is it even possible? I'm guessing within a shader it could be done by just mulitplying the sampled texture at each stage with the current screen value, but tbh I'm not sure if that is accessible to the shader (or how). My current code works, but it bugs me I'm drawing 4 huge textures separately :D

Lets look at what your blend setup is actually doing:

PDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
PDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);


This means that your colors are calculated by:

ScreenPixel = OldPixel + NewPixel * NewPixel.a


So expanding your blend sequence gives you:

color += texture1 * texture1.a;
color += texture2 * texture2.a;
color += texture3 * texture3.a;
color += texture4 * texture4.a;


This is trivial to simplify down to a single blend operation, you sum the textures:

color = texture1 * texture1.a +
texture2 * texture2.a + // Multiply by SRCALPHA manually
texture3 * texture3.a + // as you sum your texels.
texture4 * texture4.a;


Now your alpha is premultiplied as well and uses a simpler blend function:

PDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE);
PDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

• Thanks for the reply but I'm not sure how that is doable in a shader 9I'm not a shader expert). I am drawing a sprite to the screen, using a shader, how can I even get the value of OldPixel? surely it's inaccessible? ..actually it is close if I just do + for each texture, just have a mip/map bilinear issue now... – cliffski Sep 4 '13 at 9:01
• If you are rendering straight to the frame buffer you will have to use blending still, but once instead of 4 times. Although rendering to a texture could be a better approach, in that case you can just read "OldPixel" from that texture, and do away with blending. – MickLH Sep 4 '13 at 21:29

Tuns out that this is not accurate, as it's for a different alpha blend type.

I'm going to give this one a shot, primarily since there aren't any answers for you yet. I'm not completely certain that my answer is accurate or complete, but it's going to be too big to supply as a comment, so here we go.

The biggest feature you need to preserve is the order-dependent nature of these draw calls. A completely opaque texture drawn on top of another completely opaque texture demonstrates the importance of preserving the order.

I've sketched out some equations that will work, or at least be close.

What I propose trying is this: (assuming your colors are floats from 0 to 1)

For each texture you take the existing color for that pixel, multiply by (1-a), where a is the alpha of your new texture, then add in (a*newColor), for each channel. Then the total alpha of the resulting texture will be (1 - product(1-a(i))).

In other words:

for each Texture(Color,a)
TotalAlpha*=(1-a)
for x=0 to 2
colorOut(x)=colorOut(x)*(1-a) + Color(x)*a

TotalAlpha=1-TotalAlpha

RESULT: (colorOut(1),colorOut(2),colorOut(3),TotalAlpha)


Again, this is just some equations I've sketched out, but I think it's pretty close.

• That's not what his code is doing, his code clearly does additive blending. D3DRS_DESTBLEND is D3DBLEND_ONE not D3DBLEND_INVSRCALPHA like you assumes. – PeterT Sep 2 '13 at 13:56
• Ahh, yeah, you're right. I missed that, and assumed the inverse alpha blend, as that's a fairly intuitive one. – TASagent Sep 2 '13 at 14:06