Exponential shadow map filtering (in log space).

It turns out that I was performing a linear filter on my shadow map depths, but I should have been doing the shadow map prefiltering in log space. Whoops.

Actually, there seems to be a lot of confusion about how to filter in log space when outputting linear depth, and why you need to. Let me clear things up.

There are two approaches to exponential shadow mapping:

1. Output exponential light depth map, prefilter linearly.

You can create a depth map for the light by outputting exp(depth) and then perform the typical gaussian/box/tent prefiltering as normal. In this case, when drawing the light, the ESM filtering is done like so:

float occluder = tex2D(shadowMap, texCoords);
float lit = occluder / exp(c*reciever);

The advantage of this method is that the prefiltering is less expensive than the next method. Hardware bilinear, anisotropic and mip filtering all automatically filter the shadow map correctly.

The disadvantage of this method is that it is precision hungry, because exp(depth) varies quickly. A 32 bit floating point texture allows a value of c=88 before overflow errors start to occur. 16 bits is not enough precision with this method in my experience as contact light leaking is very problematic.


2. Output linear depth, prefilter in log space.

You can create a depth map for the light by outputting depth as is, and then perform a gaussian/box/tent prefilter in log space. In this case, the when drawing the light, the ESM filtering is done like so:

float occluder = tex2D(shadowMap, texCoords);
float lit = exp(c*(occluder - reciever));

The advantages of this method:

  • Less precision is required to store depth, which now varies linearly. So we only need 16 bits to store depth!
  • The ESM filtering when drawing the light is slightly faster as we can remove a division.

The disadvantages:

  • Hardware bilinear and anisotropic filtering will introduce some error, although it is generally close enough — the artifact is just a little bit of shadow overdarkening.
  • Prefiltering must be done in log space, which is slower (see below).
  • If mipmaps are used, they must be generated using filtering in log space as well.

Overall, these two methods represent a tradeoff between memory and ALU, with method 1 requiring more memory and less ALU overall.

So how and why do we filter in log space?

Consider for example that we wish to average two values in the shadow map (like in a 2×2 separable gaussian or box blur). Then we are averaging the two values exp(d1) and exp(d2).

Using gaussian/box blur weights w1 and w2, we then have:

w1*exp(d1) + w2*exp(d2)

exp(d1) * (w1 + w2*exp(d2 – d1))

exp(d1) * exp(log(w1 + w2*(exp(d2-d1))))

exp(d2 + log(w1 + w2*exp(d2-d1)))

So now the sum of the two exponentials is written as one exponential. Taking the log of the previous statement, we can perform the averaging of the box/gaussian blur by working on the exponentials argument only:

d2 + log(w1 + w2*exp(d2-d1))

So we filter the arguments of the exponentials, and then go back to exponential space when actually drawing the light, using exp(c*(occluder – reciever)) as we saw earlier.

I’ve generalized the above reasoning for two arguments to arbitrarily many arguments in the following code that can perform a box blur in log space, using an HLSL pixel shader. To perform a Gaussian blur instead, just replace the constant sample weights and 1.0 with the appropriate Gaussian weights.

sampler TextureSampler : register(s0);
#define SAMPLE_COUNT 3
float2 Offsets[SAMPLE_COUNT];

float log_space(float w0, float d1, float w1, float d2){
	return (d1 + log(w0 + (w1 * exp(d2 - d1))));

float4 Blur(float2 texCoord : TEXCOORD0) : COLOR0
	float v, B, B2;
	float w = (1.0/SAMPLE_COUNT);

	B = tex2D(TextureSampler, texCoord + Offsets[0]);
	B2 = tex2D(TextureSampler, texCoord + Offsets[0]);
	v = log_conv(w, B, w, B2);

	for(int i = 2; i < SAMPLE_COUNT; i++)
		B = tex2D(TextureSampler, texCoord + Offsets[i]);
		v = log_conv(1.0, v, w, B);

	return v;

So what does all this extra work get us? The error introduced by filtering linearly instead of in log space was so small in my tests, that I couldn’t produce a screenshot that clearly demonstrates it.

Here is the thread that inspired me to try filtering in log space, and also a clear difference between linear and log filtering is demonstrated.

I found that on the Xbox 360, log filtering didn’t cost anything extra, as the prefiltering step was texture bandwidth bound anyway — so I’ll leave it in place for now.

This entry was posted in Coding and tagged , , , . Bookmark the permalink.

10 Responses to Exponential shadow map filtering (in log space).

  1. Trosztel says:

    B2′s offset should be 1 instead of 0.

  2. Patapom says:


    In the list of advantages of paragraph 2, I disagree:
    1.0 / exp(c*receiver) = exp( -c*receiver ) so you don’t really gain a division since you don’t really need it in the first place. ;)

  3. Patapom says:

    Some other note:
    I do not quite agree you need a lot of precision to achieve an honorable result since you could perfectly write exp( -k * z ) in your shadow map (instead of exp( k * z )), whatever k you’re using the result will be in ]0,1]. I’m doing this in my test application and writing results into an R16_UNORM and I don’t have any problem with precision.

    My main concern at the moment is to find how to remove self-shadowing as can be seen in the image there: http://i.imgur.com/IKcByTC.png
    The problem is that I’m using ESM to simulate an area light so I’m also using paraboloïd projection and I fear that transformation into paraboloïd space distorts objects too much so they receive their own shadow…
    I’m a real lamer when it comes to shadow maps, I never quite took the time to study the subject because I simply… don’t like it (too many methods, too many caveats, etc.) (a bit like the zillion papers that were publised on SSAO in fact).
    I’m trying to add / remove some bias values here and there without really thinking about it and I always end up with a bad result, do you have any clue on how to remove self-shadowing?

  4. 3、 《工作安排》着重强调健全法规标准,把食品安全作为综合执法的首要责任。可怕的是不懂却听不进反对意见。50美元/吨;60-63%的合同656.79万吨,环保、石化、卫生、医药、食品安全、科研教育以及各级质检、进出口检验检疫、商检机构的仪器设备大量依赖进口。国家层面高度重视国产仪器的制造应用和推广工作,为辽宁(丹东)仪器仪表产业基地公共研发服务平台又增添了一项对外服务功能,平台的运行,仅需要1秒钟。

  5. Pingback: Shadow Map 原理和改进-SRE空间

  6. Bernardo says:

    I am actually happy to glance at this web site posts which includes plenty
    of valuable data, thanks for providing such data.

  7. WҺat’s up, just wanted tto say, I liked thіѕ article.
    It was inspiring. Keep on posting!

  8. Jacelyn1992 says:

    This post is on 19 spot in google’s search results, if you want more traffic, you should build more
    backlinks to your blog, there is one trick to get free, hidden backlinks from authority forums, search on youtube; how to get hidden backlinks from

  9. Lilia says:

    Wɦat’s Taking place i am new to this, I stumbled upon thіs Ӏ’ve discovered It absolutely helpful
    and it haѕ helped mee out lоaɗs. I am hoping to give a contribution & help othdr customers like its
    aided me. Great job.

  10. Lyle says:

    Сan I simply say what a comfort to find someoje that realⅼy knows wһat they’re discussing on tthе web.

    Υou definitely know how to brіng an issue to light and make it important.
    A ⅼott more ρeople ought to check this outt and understand this
    side of the ѕtory. It’s surprising yօu are not mopre
    populaг Ƅecause you sᥙrely hаve thᥱ gift.

Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>