It’s been a long time since my last post, but I didn’t give up on this blog. I was working really hard on a project aimed at DreamBuildPlay, but things didn’t go as I expected. It’s hard to keep everyone as motivated as you, and frustrating to see all your weeks of work being used in teapots and boxes (well, at least I have Crytek’s Sponza model).
Here is a screenshot of the editor I’ve been working on, with the game running inside it’s viewport:
It features cascade shadow maps for directional lights, spot lights (with shadows too), full HDR rendering+post processing (bloom, tone mapping and screen space lens flare), gamma corrected pipeline and SSAO, running at 60fps on XBox 360 (its a little vague since it depends on the overdraw, simultaneous shadow caster lights, etc, but it works very well).
Ok, let’s move on. On this post I will talk (and release the code as usual) about shadows, specifically for spot lights (the easiest). I’m using plain old and good shadow mapping, which basically consists in rendering the scene using the light point of view, storing the depth of each fragment on a texture (shadow map). Then, at the lighting stage, we recompute each pixel to be lit in that same light space and compare the Z of this pixel with the Z stored on the shadow map, lighting or shadowing that pixel. The full source code is here, use at your own risk.
Since I don’t want to use the PRESERVE_CONTENTS flag on any render target I use, I have to generate all shadow textures before the lighting stage begins: we cannot switch render targets from “shadow 0 -> lightAccumulation->shadow 1->lightAccumulation->etc”, otherwise we would lose its contents. The solution I’m using is:
- at the initialization stage, create the render targets for the max simultaneous lights you wanna allow to cast shadows on a single frame (you can create them at different resolutions);
- at the beginning of the rendering stage, determine visible lights;
- sort these lights, using any heuristics; I choose something like the light’s radius divided by the distance to the camera;
- select the highest rated lights as shadow casters, generate the shadows and assign them the shadow map+light view projection matrix (we could use the heuristics to select the shadow map resolution);
- render meshes to the GBuffer as usual;
- render the lights to the accumulation buffer, using the shadow information generated before
Remember that we should not draw the meshes culled by our main camera: for spot lights, we can compute a frustum using the spot cone angle and an aspect of 1.0f, and the light transform. Compare this frustum against all world’s meshes (or use any partitioning structure you like) and pick only the meshes that intersect it. The code for constructing that frustum is on Light.cs, method “UpdateSpotValues()”.
I’ve added another technique to the main shader (LPPMainEffect.fx) , that outputs the shadow for that model: I already had the technique to write to GBuffer and the one to reconstruct the lighting. This way makes easier to use some ubber shader tricks to allow alpha-masked geometry or skinned meshes, since we can use #defines to change the behavior of the three stages accordingly.
The result is here:
Soon I will port the optimizations I did on my engine: reconstruction of the Z-buffer and stencil tricks for optimized pixel lighting. I can also put the XBox version (it took me 15 minutes to fix the compilation problems, but I lost that version somewhere), although it has some useless per-frame allocations (aka iterators).
I hope you enjoy it. See ya!