Drawing silhouetted characters behind smoke particle systems

jwatte's picture

A question came up on the XNA forums: "how do I draw the silhouettes of objects in red when they are covered by smoke?"

Here is my proposal on how to use destination alpha to do it:

0) When setting up the back buffer, make sure you get destination alpha:
  graphics.PreferredBackBufferFormat = SurfaceFormat.Color; // has alpha

1) When clearing the screen, make sure the clear color has 0 alpha:

  Device.RenderStates.ColorWriteChannels = ColorWriteChannels.All;
  Device.Clear(new Color(new Vector4(0, 0, 0, 0)));

2) When rendering normal stuff, don't write alpha to the framebuffer. In the beginning of Draw, do this:

  Device.RenderStates.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;
  DrawMostOfYourOpaqueStuff();

3) When rendering anything that should be silhouetted behind particles, turn on the alpha write channels. Note that this should be done after all the other opaque stuff, else the alpha value will be kept even when there are other things (walls, etc) in front of the model:

  Device.RenderStates.ColorWriteChannels = ColorWriteChannels.All;
  DrawTheModelThatShouldBeSilhouetted();
  Device.RenderStates.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

4) Draw the particle systems as normal. Then, draw it with some additional states:

  Device.RenderStates.AlphaBlendEnable = true;
  MyParticleSystem.DrawNormal();
  Device.RenderStates.SourceBlend = Blend.DestinationAlpha;
  Device.RenderStates.DestinationBlend = Blend.InverseDestinationAlpha;
  MyParticleSystem.DrawRed();

I don't think there's much more to it than that. Clearly, anything blended needs to be drawn from back to front, and the particle system twice-drawing should be done right after each other (because they are centered on the same point).

Note that, in the ideal world, you'll have the red color be added based on both the final coverage alpha of all particles, and the destination alpha of the character/background. To make that happen, you will need to use a separate render target, though, because you can't make the blender do all those operations in a single step the way you want.