5 Comments

Shadow maps for moving light sources

A while back in one of my status updates I mentioned that I had eliminated the shadow “swimming” that occurs when moving the camera around. This resulted in a significant improvement in visual quality, and is a well-known optimization (discussed here in Moving the Light in Texel-Size Increments).

However, as soon as I put my sun/moon light into motion to get the day/night cycle moving, the swimming came back. This is caused by the same aliasing problems as the camera panning, except that there isn’t a good way to correct it. We can’t maintain the “integrity” of the light projection when the light is rotating.

My first thought to fix it was to simply move the light less often. So every few seconds we would change the sunlight position. Unfortunately, as you might guess, the “jump” when this happened was very noticeable. The sun simply moves too quickly in a game time day/night cycle (currently I’m using the same “1 minute is 1 second” time scale that Fable II uses).

My next thought was to blend the shadow map comparisons between two different light positions. This immediately brings up a host of performance issues:

  • We need an additional shadow map (another 4MB texture to resolve)
  • We need to render all the shadow-casting geometry a second time
  • This light pass for the directional light, which does fairly expensive PCF samples (9 per pixel), now needs to do twice as many

The first issue can be mitigated by re-using the same shadow map texture for both lights. I’m not sure resolves are very expensive on the PC – its probably mainly just a memory usage issue. But on the Xbox resolves require copying the data from EDRAM to main GPU memory (which if my calculations are correct, would take about half a millisecond for my shadow map). Luckily the solution is straightforward for me. I had been using a HalfVector2 surface format (two 16-bit floating point components), and just using the red channel. I figured I had the green channel free. But how could I selectively render to the two different channels? XNA doesn’t let you alpha-blend to floating point render targets, and you also can’t enable/disable writing to individual color channels (and because it doesn’t allow this, I assume that support is not widely available on different graphics cards).

Luckily, I already have support for storing depth in two 8-bit fixed point components (I use this in my G-buffer). So the solution was to switch my shadow map to using a Color surface format (A8R8G8B8) which allows control over the color channels. So to get my two shadow maps in one render target, I do the following:

  • Set the shadowmap render target as the active one
  • Clear it
  • Set color write channels to Red/Green, and render the shadow map for the first light.
  • Clear *just* the Z buffer
  • Set color write channels to Blue/Alpha, and render the shadow map for the second light.

Because I’ve disabled two color write channels in each case, I don’t need different pixel shaders for each pass, I can just write to all channels:

float4 EncodeShadowDepth(float viewSpaceDepth)
{
	// Negate because XNA is right-handed.
	float encodedDepth = (viewSpaceDepth + NearPlane) / (NearPlane - FarPlane);
	float scale = encodedDepth * 255;
	float depthR = floor(scale) / 255;
	float depthG = frac(scale);
	return float4(depthR, depthG, depthR, depthG); // Duplicate (R, G) in (B, A)
}

So that addresses the texture size issue for the dual shadow maps.

I don’t think there is any way I can get away with not rendering the geometry a second time.

For the final issue, I knew right away that doubling the number of PCF-taps I was doing was going to be too expensive. So my first implementation attempt was to restrict this to a smaller section of the screen. Basically, the left side of the screen would apply the shadow map for the “next” position of the light, and the right side of the screen would show the “previous” one. A thinner band (maybe one quarter of the screen width) would move across the screen. Only inside this band, the results from the two maps would be blended. The band needs to move across the screen in the time that we decided to jump to the next light position.

Well, that worked, except it doesn’t look that great and it’s still kind of expensive.

Just for kicks, I implemented the “full” solution – blending smoothly across the entire screen. Yes, it’s expensive, and yes it looks great.

Here’s a video that demonstrates the different techniques:

Here’s a summary of the perf costs on my crappy GeForce 8500 GT.

  • base rendering cost for the scene (single SM): 48ms
  • two SMs blended in moving band: 52ms (+4ms)
  • entire screen blend: 61ms (+13ms)

So you can see there is a significant increase in cost for the nice-looking solution.

I probably still have room in my PCF shadow mapping algorithm for performance improvements, so who knows, maybe the entire screen blend will become feasible.

There are a few alternative approaches worth investigating too.

I could go with a single shadow map that “jumps” only when the player is moving around. It’s a lot less noticeable in that case. That opens up a bunch of other issues though: what happens if the player stands still? How long do we wait before jumping the light? The longer we wait the worse it will be. What if remaining still is important to gameplay? Maybe we slow down time while the player is standing still? Will that be obvious? What if they want to less time pass?

This would open up all sorts of other gameplay issues. So I think this would not be a very robust or maintainable solution – it would cause problems later. I would prefer a one-size-fits-all simple solution.

Another option is to reduce shadow contast so the swimming is less noticeable. Have a look at Fable 2. It has the same issue as me here – shadow maps for moving light sources. The effect is less noticeable there because the shadow contrast is very low, and from what I can tell it is mostly only moving objects that cast shadows: people (which always move) and trees (which always move). These solutions would impact and limit the art direction for my game, which I don’t want to do.

So I’m not really sure how I will end up solving this.

One other idea I have is to alternate the rendering and sampling of each the shadow maps every frame. e.g. in frame 0 render the shadow map and a lightmap for position A, and then in frame 1 for position B. Then when applying the directional light, blend between the lightmap from this frame and the one from the previous frame. For fast moving objects there will definitely be artifacts, but how noticeable will they be?

Note that I am introducing a lightmap which contains the result of the PCF shadow map sampling operation. So this would be an additional full-screen render target and resolve in my current deferred renderer. If I move to a LPP (light pre-pass) renderer then I may already have room to store this value in the light accumulation buffer, and so there should be very little additional performance penalty. If I get around to trying it I will make a new post.

Advertisements

5 comments on “Shadow maps for moving light sources

  1. You’re doing a nice job with the shadows so far. The blending solution is very well done and I’d go for that one, though it’s expensive like you say. 48 ms for base rendering? I shouldn’t complain about my optimizations anymore. I use a Radeon 4670 HD in my main comp, but I also have an unused 8600 GT laying around. Wouldn’t it be great if you can hot swap video cards to quickly test things out?

    Slowing down time when the player is standing still sounds like a good option to me. In this case, maybe player is taking a short break or just wants to soak in the view. Make it a real 1 to 1 time scale in this case.

  2. On the Xbox right now its closer to 26ms for base rendering. Still not great because it doesn’t give me much leeway to meet my 30FPS goal. I tried the different shadow configurations on the Xbox. The “moving band” adds 2.5ms per frame, and the full blend added 9.5ms.

    I have two other machines to test on, a Macbook air (whose GPU performs a little better than my desktop), and another desktop machine with a Nvidia GT 240. I’ve only run the game a few times there, but I think my frame rates were on the order of ~120FPS, and from what I understand that’s not an especially high end card.

    (resolutions are 1280 x 720 in all cases)

    I kind of prefer to keep working on a machine with a poor GPU, as it keeps the pressure up 🙂

    • Using a mediocre GPU does help you get a sense for a “baseline” system configuration. The great thing about releasing games for Xbox is that all the hardware is identical. I can’t even use my Macbook that much because its GPU (Intel X3100) doesn’t support the HiDef profile.

  3. […] everything I’ve been reading indicates that there is no good solution to this problem. This blog post by Phil Fortier of Ice Fall Games seems to be the only real solution presented anywhere that I […]

  4. […] problem back in Part 15 as well. It is not a solved problem. The best solution offered can be found here. His solution is to render two shadow maps each frame and blend them together. I’m pretty […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Just another WordPress site

Just another WordPress.com site

The Space Quest Historian

Adventure game blogs, Let's Plays, live streams, and more

Harebrained Schemes

Developer's blog for IceFall Games

kosmonaut's blog

3d GFX and more

Halogenica

Turn up the rez!

bitsquid: development blog

Developer's blog for IceFall Games

Game Development by Sean

Developer's blog for IceFall Games

Lost Garden

Developer's blog for IceFall Games

Memories

Developer's blog for IceFall Games

Casey's Blog

Developer's blog for IceFall Games

Blog

Developer's blog for IceFall Games

Rendering Evolution

Developer's blog for IceFall Games

Simon schreibt.

Developer's blog for IceFall Games

Dev & Techno-phage

Do Computers Dream of Electric Developper?

- Woolfe -

Developer's blog for IceFall Games

Fabio Ferrara

Game Developer

Clone of Duty: Stonehenge

First Person Shooter coming soon to the XBOX 360

Low Tide Productions

Games and other artsy stuff...

BadCorporateLogo

Just another WordPress.com site

Sipty's Writing

Take a look inside the mind of a game developer.

%d bloggers like this: