As get deeper into my current project, which happens to be 3D vehicle-based platformer/racer loosely based on the old DOS game Skyroads, I find myself spending more and more time grappling with various headaches that come up on the 360 version. So I've decided I'm going to share some of trials and tribulations, so that maybe some people learn from them (and also so I can just plain vent about them!).
One of the early goals I set for myself when starting the project was that I wanted to make some use of modern graphics techniques, and really try to squeeze some performance out of both the 360 and PC GPU's. This meant some decent shaders, HDR, 4xMSAA 1280 x 720 @ 60fps, and of a good shadowing implementation. Shadows are a topic that I keep near and dear to my heart, so I'm going to talk about them in my first entry.
When first planning out my renderer, I came up with the following choices for my shadow implementation:
- Stencil shadows
+Pixel-perfect shadows, no resolution issues
-Requires multi-pass for any lights needing shadows
-Hard edges are ugly!
-Softening is very expensive
-Tied to geometric complexity
-Not really any active research on the subject...even Carmack ditched them
- Shadow maps, with standard PCF
+Very commonly used, lot's of known optimizations
+Can soften edges and give good filtering when enough samples are used
+Can be used with an R32F texture, which gives good precision
-No hardware filtering
-PCF samples are incredibly expensive on the 360, which has very limited bandwidth
-Biasing artifacts are very common
- Variance Shadow Maps
+Can use hardware filtering
+Can be used with multi-sampling, which is "free" on the 360
+Can be pre-filtered, for example with seperable gaussian blur
-Very precision-hungry, in most cases R32G32F is needed (which doubles bandwidth per read)
Ultimately I decide to go with VSM's first, and then try other approaches later. VSM is capable of producing some tremendously good-looking shadows thanks when anisotropic filtering and a gaussian blur are used. Plus being able to enable MSAA very cheaply on the 360 made it seem like a very good fit for the hardware, especially considering how expensive PCF can be. I remember seeing Wolf mention that he was limited to 6 or so PCF taps on the consoles...IMO that's not enough to produce high-quality shadows in many situations. Later on I confirmed this: using higher numbers of samples (16, 25) was enough to bring my framerate crashing down on the 360. VSM has not been without it's headaches though...the anti-aliasing may be cheap but the blurring sure isn't. Even using a blur with just 9 taps is enough to cause quite a noticable framerate drop, which unfortunate considering the improvement in quality it brings.
One trick I've found to be quite effective on the 360 is deferred shadow maps. The technique is one I first saw mentioned by nAo over on the beyond3d forums, and it involves rendering the results of a shadow-map comparison to a texture in a seperate pass before doing the full lighting pass. It basically works like this:
-Have all geometry render linear depth to a screen-sized texture
-Render the shadow-map
-In a full-screen pass: sample depth from the depth texture, reconstruct view space, convert to light-space, and compare depth with the shadow-map
-Use the shadow occlusion texture to attenuate lighting in the actual main pass
The reason it's a win performance-wise, is because when you perform a full-screen pass you ensure that every quad of pixels is fully utilized. This isn't the case when you're rendering a bunch of 3D geometry, since the triangles often won't overlap all 4 pixels of a quad. It's all very nice from a pure design standpoint: your shadowing is completely decoupled from your lighting. Your fancy normal-mapping or sub-surface scattering don't need to be aware at all of what shadowing method you're using, it just needs to sample the final attenuation value from a texture. Plus you don't need to have two versions of those shaders: if shadows are disabled, you can just feed the lighting shaders a 1x1 white texture. Having a depth texture available also usually isn't a big deal, since pretty much every modern engine needs depth for just about every friggin' effect.
As of now with this shadowing system in place, I have everything running at a little over 60 fps with a 1024 x 1024 shadow map being rendering with 4xMSAA. In order to keep my framerate high enough to remain at a steady 60fps with vsync, I'm defintely going to need to find some more optimizations and the shadowing system is certainly somewhere I'm going to come back to. However at the moment, I'm quite satisfied with the results.