views:

893

answers:

3

Essentially, what I want to do (in DirectX) is to take two partially-transparent images and blend them together. This works fine with default blending, insofar as they both show up as overlapping, etc. However, the problem is that the opacity goes up markedly where the two intersect. This causes increasing problems as more sprites overlap. What I'd like to do is keep the blending the same, except keep a global opacity for all these sprites being blended, regardless of how they overlap.

Seems like there would be a render setting for this (all of these sprites are alone in their sprite batch, which keeps that part easy), but if so I don't know it. Right now I'm kind of shooting in the dark, and I've tried a lot of different things and none of them have looked right at all. I know I probably need some sort of variant of D3DBLENDOP, but I just don't know what sort of settings there I really need (I have tried many things, but it is all guessing at this stage).

Here is a screenshot of what is actually happening with standard blending (the best I can get it): http://arcengames.com/share/FFActual.png Here is a screenshot with a mockup of how I would want the blending to turn out (the forcefields were added to the same layer in Photoshop, then given a shared alpha value): http://arcengames.com/share/FFMockup.png

This is how I did it in Photoshop: 1. Take the two images, and remove all transparency (completely transparent pixels excepted). 2. Combine them into one layer, which blends the color but which has no partial alpha at all. 3. Now set the global transparency for that layer to (say) 40%.

The result is something that looks kind of blended together color-wise, but which has no increase in opaqueness on the overlapped sections.

UPDATE: Okay, thanks very much to Goz below, who suggested using the Z-Buffer. That works! The blending, by and large, is perfect and just what I would want. The only remaining problem? Using that new method, there is a huge artifact around the edge of the force field image that is rendered last. See this: http://www.arcengames.com/share/FFZBuffer.png

UPDATE: Below is the final solution in C# (SlimDX)

  1. Clearing the ZBuffer to black, transparent, or white once per frame all has the same effect (this is right before BeginScene is called)

Direct3DWrapper.ClearDevice( SlimDX.Direct3D9.ClearFlags.ZBuffer, Color.Transparent, 0 );

  1. All other sprites are drawn at Z=1, with the ZBuffer disabled for them:

device.SetRenderState( RenderState.ZEnable, ZBufferType.DontUseZBuffer );

  1. The force field sprites are drawn at Z=2, with the ZBuffer enabled and ZWrite enabled and ZFunc as Less:

device.SetRenderState( RenderState.ZEnable, ZBufferType.UseZBuffer ); device.SetRenderState( RenderState.ZWriteEnable, true ); device.SetRenderState( RenderState.ZFunc, Compare.Less );

  1. The following flags are also set at this time, to prevent the black border artifact I encountered:

device.SetRenderState( RenderState.AlphaTestEnable, true ); device.SetRenderState( RenderState.AlphaFunc, Compare.GreaterEqual ); device.SetRenderState( RenderState.AlphaRef, 55 );

Note that AlphaRef is at 55 because of the alpha levels set in the specific source image I was using. If my source image had a higher alpha value, then the AlphaRef would also need to be higher.

+1  A: 

You can specify the D3DBLENDOP used when blending the two images together for the alpha channel. It sounds like your using D3DBLENDOP_ADD currently - try switching this to D3DBLENDOP_MAX, as that will just use the opacity of the "most opaque" image.

Reed Copsey
Thanks -- I have tried that, actually. It does not seem to change the result at all, which is strange. I've also tried messing with the Alpha function and the reference value for it, but have not been able to get anywhere with that, either. It's curious to me that there is literally NO difference with D3DBLENDOP_MAX versus add, it makes me wonder if there is some other setting I need to set in conjunction with it? Nothing I have tried has worked, though.
x4000
Make sure to set D3DRS_ALPHABLENDENABLE=TRUE. If it is, this should work, provided D3DPMISCCAPS_BLENDOP is supported in your device caps. (Some older graphics cards don't support anything but blend add). For details, read the fine print at: http://msdn.microsoft.com/en-us/library/bb172599%28VS.85%29.aspx
Reed Copsey
That is on, and I'm using an 8800GTS. I think maybe this is an operations order problem of some sort -- as each image is added to the buffer, the buffer's opacity is going up or something like that. I'm really not an expert on this aspect of graphics programming. The problem is that, by the time there are around 12 overlaps, these partially-transparent images form a completely opaque mass.
x4000
Make sure to set source and dest blend functions. THere are many options: http://msdn.microsoft.com/en-us/library/bb173417%28VS.85%29.aspx
Reed Copsey
I know -- that's what I'm trying to find out, is the options to use. I've tried a huge number of combinations, with no results for this specific problem.
x4000
One option (linear blend between color, including alpha values): lpD3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCCOLOR); lpD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCCOLOR);
Reed Copsey
I just saw your latest note -- don't know why it did not show it before. When I use that linear blend, it makes it extremely opaque (just the barest of transparency remaining, and it's still uneven alpha on the overlaps). That's one of the combinations I had already tried, but I tried it again just to be sure. No dice...
x4000
post a screenshot?
Goz
Okay, here is a screenshot of what is actually happening with standard blending (the best I can get it): http://www.arcengames.com/share/FFActual.pngHere is a screenshot with a mockup of how I would want the blending to turn out (the forcefields were added to the same layer in Photoshop, then given a shared alpha value): http://www.arcengames.com/share/FFMockup.png
x4000
+1  A: 

It is hard to tell exactly what you are trying to accomplish from your mock up since both forcefields are the same color; do you want to blend the colors and cap the alpha? Just take one of the colors?

Based off the above discussion it isnt' clear if you are setting all the relevant render states:

D3DRS_ALPHABLENDENABLE = TRUE (default: FALSE)

D3DRS_BLENDOP = D3DBLENDOP_MAX (default: D3DBLENDOP_ADD)

D3DRS_SRCBLEND = D3DBLEND_ONE (default: D3DBLEND_ONE)

D3DRS_DESTBLEND = D3DBLEND_ONE (default: D3DBLEND_ZERO)

It sounds like you are setting the first two, but what about the last two?

Andrew Khosravian
Unfortunately, that still doesn't work. This is what I get: http://www.arcengames.com/share/FFZeroOne.pngThat's actually with D3DRS_DESTBLEND as D3DBLEND_ZERO by mistake, but the D3DBLEND_ONE is 100% identical to it.
x4000
Oh -- and as for what I am trying to accomplish, this is how I did it in Photoshop:1. Take the two images, and remove all transparency (completely transparent pixels excepted).2. Combine them into one layer, which blends the color but which has no partial alpha at all.3. Now set the global transparency for that layer to (say) 40%. The result is something that looks kind of blended together color-wise, but which has no increase in opaqueness on the overlapped sections.
x4000
+1  A: 

Best I can tell is that the forcefields are a whole object. Why not render them last, in front to back order, and with Z-buffering enabled. That will give you the effect you are after.

ie its not blending settings thats your problem at all.

Edit: Can you use render-to-texture then? IF so you could easily do what you did under photoshop. Render them all together into the texture and then blend the texture back over the screen.

Edit2: How about

ALPHATESTENABLE  = TRUE;
ALPHAFUNC = LESS

ALPHABLENDENABLE = TRUE;
SRCBLEND = SRCALPHA;
DESTBLEND = INVSRCALPHA;

SEPERATEALPHABLENDENABLE = TRUE;
SRCBLENDALPHA = ONE;
DESTBLENDALPHA = ZERO;

You need to make sure the alpha is cleared to 0xff in the frame buffer each frame. You then do the standard alpha blend. while passing the alpha value straight through to the backbuffer. This is, though, where the alpha test comes in. You test the final alpha value against the one in the back buffer. If it is less than whats in the backbuffer then that pixel has not been blended yet and will be put into the frame buffer. If it is equal (or greater) then it HAS been blended already and the alpha value will be discarded.

That said ... using a Z-Buffer would cost you a load of RAM but would be faster overall as it would be able to throw away the pixels far earlier in the pipeline. Seeing as all the shields would just need to be written to a given Z-plane you wouldn't even need to go through the hell I suggested earlier. If the Z value it receives is less than whats there already then it will render it if it is greater or equal then it will discard it, fortunately before the blend calculation is ever performed.

That said ... you could also do it by using the stencil buffer which would require a Z-buffer anyway.

Anyway ... hope one of those methods is of some help.

Edit3: DO you render the forcefield with some form of feathering around the edge? Most likely that edge is caused by the fact that the alpha fades off slightly and then the "slightly alpha" pixels are getting written to the z-buffer and hence any subsequent draw doesn't overwrite them.

Try the following settings

ALPHATESTENABLE = TRUE
ALPHAFUNC = GREATEREQUAL // if this doesn't work try less .. i may be being a retard
ALPHAREF = 255

To fine tune the feathering around the edge adjust the alpharef but i'd suspect you need to keep it as above.

Goz
The forcefields are two separate objects, that's the problem. And there could be dozens of them, some overlapping some not. I'm not using Z-buffering at all, given that this is just 2D (even if it is in Direct3D), and all of the sprites have a Z position of 0. These forcefields are in their own batch being sent to the card as a group and blended as a group, but that's it. So it seems like I should be able to combine them in some manner, given that.
x4000
I still reckon Z-Buffer methodd would give best performance ... but I've given you an alpha test based alternative :)
Goz
Wow, thanks for all the great suggestions here! SEPERATEALPHABLENDENABLE is new to me, that seems promising. However, with the settings you have above, I get zero drawn. When I then set the AlphaRef (as it is called in SlimDX, don't know the name of the C++ equivalent) to 0xff, then it shows up exactly like the standard image FFActual above. Not sure why that would be, exactly, I am not an expert on the Direct3D internals.
x4000
For the Z buffer, that is also something I have never worked with, and I don't know exactly how that would work with a 2D game. Basically I tried turning on the Z Buffer (simple enough), but that did not do anything noticeable. Everything has a Z value of 0, so I guess that makes sense. So I changed the Z value of the force fields to be 1, and that didn't change anything either. When it comes to stencil buffers and so on, I have no experience with that either; there are tutorials around, and I will look at them now, but all of them are trying to do really different things from me.
x4000
Damn my error ... i forgot that D3D does not support destination alpha testing. In which case your best option really is to use a Z-Buffer. Basically you need to pass a Z-Buffer value directly through to the Z-Buffer. It is basically as easy as setting the Z-Value with an appropriate matrix and and appropriate set of Viewport settings. You can then render everything to z = 2 (for example) and then render all your alpha stuff to z=1 with D3DCMP_LESS set on the Z test and Z-writes enabled.
Goz
You could also easily do it with the stencil buffer ... but getting your head round the Z-buffer will not only be the fastest but the best way. I'll put up some code a bit later.
Goz
Hey, that works! I'm still fighting with some quality issues that it is having, but I've got it basically rendering except with a bunch of artifacts and garbage from the clears not being exactly right. But the blending is perfect!I'll mark this as closed as soon as I get the quality issues sorted, I may have a last few couple of (hopefully brief) questions to get resolved on this. But great thanks so much already!
x4000
well edit your original question with any further questions and i will endeavour to answer them :)
Goz
Added it into the original question, with a screenshot of the one remaining artifact. I think we almost have it, at long last! :)
x4000
Well give that a try. Hope that sorts ya out :) I probably won't find out if it does until tomorrow now ...
Goz
Beautiful! That did it! I'll post the solution and award this now. Thanks for all your help, that was great!
x4000