Easy Transparent Shadow Maps

transparent_shadows

Supporting transparencies with traditional shadow mapping is straight forward and allows for nice effects but as with anything related to rendering transparents with rasterization, there are corner cases.

Little sneak peak of what you can achieve with this:

The implementation is really simple once you have implemented shadow mapping for opaque objects. After the opaque shadow pass, we must render the transparents into a color buffer, but reject samples which would be occluded by opaques, so using a depth read-only depth stencil state. The transparents should be blended multiplicatively. Sorting does not matter with a multiply blend state. In bullet points:

  1. Render opaque objects into depth stencil texture from light’s point of view
  2. Bind render target for shadow color filter: R11G11B10 works good
  3. Clear render target to 1,1,1,0 (RGBA) color
  4. Apply depth stencil state with depth read, but no write
  5. Apply multiplicative blend state eg:
    • SrcBlend = BLEND_ZERO
    • DestBlend = BLEND_SRC_COLOR
    • BlendOp = BLEND_OP_ADD
  6. Render transparents in arbitrary order

When reading shadow maps in shading passes, we only need to multiply the lighting value with the transparent shadow map color filter if the pixel is inside the light. There is a slight problem with this approach, that you will notice immediately. Transparent objects now receive their own colored self shadow too. The simplest fix is to just disable the colored part of the shadow calculation for transparent objects. We can already produce nice effects with this, this is not a huge price to pay.

See it in action, transparents are rendered without colored self-shadows:

doesnotreceive

But they receive shadows from opaque objects just fine:

opaquereceived

There is a technique which would allow us to render colored shadows on top of transparents too. This involves keeping an additional shadow depth map for transparencies. The flow of this technique is like this (from a Blizzard presentation):

  1. Render opaque shadow map
  2. Render transparent shadow map
    • To a separate depth texture!
    • Depth writes ON
  3. Clear shadow color filter texture (like in the simple approach)
  4. Render transparent again to color filter render target
    • But use the opaque shadow map’s depth stencil
    • depth read ON
    • depth write OFF

And in the shading step, now there will be two shadow map checks, one for the opaque, one for the transparent shadow maps. Only multiply the light with the shadow filter color texture when the transparent shadow check fails.

In my experience you can also have the transparent depth in the color texture’s alpha channel, to refine the results. (See UPDATE section at the end).

This will eliminate false self shadows from transparent objects. But unfortunately now when a transparent receives a colored shadow, its own transparent shadow color will also contribute to itself.

What’s more interesting, are the additional effects we can achieve with transparent shadow maps:

Textured shadows, which can be used as a projector for example, just put a transparent textured geometry in front of a light:

textured_shadows

And underwater refraction caustics:

caustics

UPDATE: You can use the alpha channel of the transparent shadow map as a secondary depth buffer. This way, tinting volumetric light becomes possible because light between light and transparent object must be not tinted (rejected based on secondary depth). Since the multiplicative colors don’t care about alpha at all (however, the color is multiplied with alpha – 1 as a transparency factor), this is safe to use with a MAX blend operator (or MIN if you don’t use reversed Z):

SrcBlendAlpha = BLEND_ONE;
DestBlendAlpha = BLEND_ONE;
BlendOpAlpha = BLEND_OP_MAX;
Also use a texture format with alpha channel such as R16G16B16A16_FLOAT

The secondary depth buffer will support one layer of transparency (it should be the closest transparent layer to the light), so it’s not perfect, but things rarely are in real time rendering!

That’s it, I think this is a worthwhile technique to include in a game engine. This can be used for many interesting effects. Thank you for reading!

My transparent shadow map rendering pixel shader can be found here.

Related article:
StarCraft 2 Effects and techniques

turanszkij Avatar

Posted by

10 responses to “Easy Transparent Shadow Maps”

  1. Hi
    I found a trick to have transparent shadow not cast on the emitter with the simple approach.
    Because the a channel of the colored shadow is not used i put in this the index number of the object casting the shadow (let say yo uahve two caster, index are 0 and 0.1f
    On the final pass i used this alpha channel to compare with the current object being rendered. currentobject.index = alpha do not cast shadow. Of course the shadow is casted on other object including transparent ones
    I use index of object as float as conversion to A8 channel is simpler to decode. Thus i can have a maxilum of 11 transparent caster in a given scene (the opaque object are not includued in the numbering) with indexes ranging from 0.0 to 1.0 step 0.1.
    A small drawback: the colored shadow of a tranparent object can be modulated by the one from another transparent object depending on their overlap from the light point of view.
    I can send a picture on request.
    let me know if you find this of interest.
    PhilB

    1. Thanks for the suggestion. If you use the 8-bit alpha channel, you can have 256 different casters though, not just 10. The indexing step will be 1.0f / 255.0f in this case 🙂
      I also think this would be problematic with blending shadows, it will mess up the indexing. Maybe that’s what you mean by the incorrect overlapping?

  2. Turánszki, absolutely loving learning from your engine. Quick question on transparent shadow maps, really trying to understand the concept here, any way you have the scene shown in the images above saved around?

    1. That scene is no longer available (only in source control, in the old-system-backup branch). It should be easy to try it in the engine. You will have to modify a material to be alpha blended and reduce its transparency, and also enable transparent shadows in the renderer settings.

  3. Nice work! A quick question, since you are not writing the depth of the transparents, what if there is a pixel to shade between the light source and the transparent? This pixel should not be coloured by the transparents but there is no depth to distinguish it? many thanks!

    1. There are two cases:
      1) if it is opaque, then it’s shadow depth will simply occlude the transparent, so it will be rendered correctly
      2) if it is transparent, then it simply doesn’t receive a transparent shadow map as any other transparent. It will however still contribute to transparent shadow map with multiply blend state.

  4. This is an awesome post. Thank you! I have a quick question. Since you are using the “read only” opaque shadow map as the depth test, wouldn’t you get a white outline around the alpha shadows when you “soften” the opaque shadows using pcf or pcss? Since in the soft areas the shadows are not fully black but due to the depth test, no alpha shadows are written there.

    1. Yes, this is an issue when opaque shadow is blocking transparent object shadow. Maybe you can disable depth test for transparent shadow map and rely on secondary depth check so opaque before transparent caster still doesn’t receive transparent shadow.

  5. Hi!
    Did I understand correctly that “But unfortunately now when a transparent receives a colored shadow, its own transparent shadow color will also contribute to itself.” means that, for example, if there’s a green window on the front (opacity = 0), and other white translucent object behind it (opacity 1), the object behind the window will be black (instead of green) since ‘Filter color’ is black because of `color = color * (1 – opacity)`?

    1. Opacity 0 will lerp towards white if I remember correctly so if passes through the multiply blend. You can try in the engine quite easily though.

Leave a Reply

Discover more from Wicked Engine

Subscribe now to keep reading and get access to the full archive.

Continue reading