XNA 4.0 Light Pre-Pass: casting shadows

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!

J.Luna

About jcoluna

Game developer and musician
This entry was posted in XNA and tagged , , , . Bookmark the permalink.

39 Responses to XNA 4.0 Light Pre-Pass: casting shadows

  1. Christian says:

    Great that you’re back. Will check the new code out. Any chance on that you will cover mesh animation soon, because that’s what I really found no information on, at least none how to do it in a LPP renderer.

    Keep up the great work.

    • Animation is done identically in a forward rendered compared with a LPP setup (or using other rendering techniques). So I don’t think that this is the right place to ask about animation techniques.

  2. Christian says:

    My copy of Visual C# Express does not compile it as it is. It’s always searching for a certificate, it does not find. For everybody else encountering this problem, go into the startup project’s properties and go to “Signing” and uncheck the ClickOnce checkbox. Start debugging again, confirm with “Ok” and it should run.

  3. John C Brown says:

    Excellent sample. Best lighting example with code that I have found so far.

    Thanks

  4. Feedidy says:

    Amazing sample. The performance is spot on, even on a 360.

    Could you discuss Alpha Masking in your next post? And perhaps how you’d use particles with this lighting engine.

  5. Pingback: XNA 4.0 Light Pre-Pass | J.Coluna

  6. Pingback: Windows Client Developer Roundup 068 for 5/9/2011 - Pete Brown's 10rem.net

  7. Have you had any success in improving the lighting quality on the XBOX 360?

    If you dont have an XBOX, then modify your lightBuffer to use “SurfaceFormat.Rgba1010102” instead of RGBA64 (XNA automatically uses a different format on the XBOX despite your selection of RGBA64). I have tried (briefly) to implement manual bilinear filtering in LPPMainEffect.fx, without any quality improvement. I assumed that the cause of the poor lighting quality on the 360 was due to a lack of 1010102 format hardware linear sampling. If it is, then maybe I made a mistake in my bilinear filtering implementation.

    This LPP sample is pretty cool, but in it’s current state, ultimately not very useful if one wishes to target the XBOX 360 (just in my opinion).

    • I realize now that it was a little silly to assume that bilinear filtering would help much as the bands are quite large. The banding is due to the lack of precision in the 1010102 format, I suppose. I guess that doesn’t leave us with very many options, as only 32 bit render targets are supported on the XBOX, and 1010102 is the only blendable floating point format.

      Do you know of any solutions to the banding problem on the XBOX?

    • Nevermind. Understanding the function of the “magic number” used to avoid oversaturation clamping allowed me to resolve this issue, as I’m sure you already know.

      • jcoluna says:

        Hah, you got it =)

      • Eric Bailey says:

        Mind sharing the solution? I’ve been looking at this for the 360, but haven’t discoverd the solution…

      • jcoluna says:

        On shaders LPPMainEffect.fx and LightingLPP.fx, we have these variables:

        const static float LightBufferScale = 0.01f;
        const static float LightBufferScaleInv = 100.0f;

        they are used to prevent the lighting to clamp on [0..1] range, so we can fake [0..100]. Change it to 0.1f and 10.0f, it should work for the 10bit lighting accum buffer.

        See ya!

  8. Two things:

    1. You should put a donate button somewhere on your site. If you post decent/useful source code, someone is going to donate. Like me.

    2. When are you going to post the next version of this code that you mentioned?🙂

  9. Eric D. Bailey says:

    Thanks for the sample and the work. I’m just getting back into game development and your example has been great for understanding XNA and Light Pre-pass. Keep up the good work!

  10. Just a visitor says:

    Really love your ‘hands on’ example of LPP. Thank you for sharing! And +1 for the donate button idea, think your examples are worth it.

    What about the License for your code? Is it free?

    And a more code related question:
    Are there specific reasons for bringing spot & point lights into view space on CPU? Is this possibly an artefact from development / try-out sessions? Just don’t see a cause for not doing it in the shader.

    Would realllly like to see more

  11. In DirectionalLightVS, in LightingLPP.fx you are adding GBufferPixel size to ouput.TexCoord. Are you sure that’s correct? I don’t see why you would add this offset there. Did you perhaps mean to add half of the pixel size to sample the center of a pixel in the DX9 coordinate system? (You are already doing this in the pixel shader though, so I’m not sure if this is just an error.)

    • Hmm. Adding GBufferPixel size produces correct lighting. I still don’t see why we need this offset, but I’m not that familiar with your code yet too🙂

    • Sigh, nevermind, I see that GBufferPixel size is actually half a pixel, so this makes sense, to center sampling on pixels as per the DX9 coordinate system requires🙂

      • jcoluna says:

        Hey, I didn’t even have time to answer haha!
        Btw, are you testing on xbox? I think I will have to use 2 light buffers, diffuse and specular, since RGBA64 doesn’t seem to work on xbox. 2 bit alpha (from R10G10B10A2) is not enough for specular:/
        Any suggestion?
        See ya!

  12. Killy says:

    I absolutely love your articles!
    Written very well and easy to understand, keep up the awesome work!

    Thank you,
    greetings from Germany

    • To answer you about the 2 bits for specular problem:

      I am currently not using a specular channel, and accumulating specular in the light buffer RGB channels. This means that you can’t use a specular strength map, and it means that you can more easily exceed the HDR “overbright” limits, since specular highlights are often close to full brightness. However, it does allow you to have colored specular highlights, which a single specular channel does not.

      One idea I’ve been considering to work around this, is to pack the specular into the 1010102 format using the 2 bit alpha, plus 1 bit in R and 1 bit in B. Then, I would apply half of the specular to the RGB channels, and store the other half in RBA (4 packed bits total).

      Then your specular strength maps can at most reduce specular highlights by half.

      However, if we are willing to sacrifice some range on the specular highlights, and use only 6 bits for specular, we can apply 25% of the specular to the RGB channels, and then store the remaining 75% of the specular packed in RBA (4 bits worth). This would allow specular strength maps to reduce specular highlights by up to 75%, which is probably enough for decent results.

      One drawback to this method is that we lose 1 bit of red and 1 bit of blue channel information. This means that for red and blue, lights can more easily reach the HDR overbright limit.

      Which method suits your purpose depends on your application.

      Part of the benefit of the LPP method, is that we can avoid much of the texture bandwidth requirements that a full defferred setup requires — making this a better fit for the XBOX. For this reason, I’ll be avoiding using a second texture to store specular.

  13. Pingback: Deferred lighting. | Olhovsky

  14. Robot97 says:

    I have something wrong with the lights:

    • jcoluna says:

      That happened on my friend’s PC that doesn’t support rgba64 render targets. Change the light accumulation RT to HdrBlendable, and make sure to change the shaders to use a smaller “magic number”. Quoting my explanation some replies ago:

      “On shaders LPPMainEffect.fx and LightingLPP.fx, we have these variables:

      const static float LightBufferScale = 0.01f;
      const static float LightBufferScaleInv = 100.0f;

      they are used to prevent the lighting to clamp on [0..1] range, so we can fake [0..100]. Change it to 0.1f and 10.0f, it should work for the 10bit lighting accum buffer. ”

      See ya!

  15. Marco says:

    Hi!

    I tried your example and it’s very impressive🙂

    It’s possible to render transparent objects or alpha textured polygons like foliage without using forward rendering?

    Thank you😉

    Marco

  16. Raziel Nomack says:

    Realy, Realy Nice JOB! Im can say, with no shadows of doubt your work will be a Nice “Framework” of light and shadow. Simple Implementation, very very easy control of all elementes and intuitive code.

    Some questions.

    1 – Why u use LightPrePassFX.fx in max and LPPMainEffect.fx in Code?
    Im Trying to make both the same code, to work in both
    Im putting more options like Normal Power, and Specular Power. And trying to make this will load in LightPrePassProcessor.cs.

    2 – How i can showa mesh with Alpha Tranparency?

    2 – How i change the texture of a mesh “on the fly”?

  17. 1)
    The shader that model’s use is completely different from the shader that is used to draw the lights.

    The shader that models use is used to output the model’s depth and normals to the normal and depth buffers — this is the “pre” in “light pre pass”.

    2)
    One of the disadvantages of deferred lighting is that it is not easy to draw transparent geometry with lighting applied. One option is to draw transparent geometry using forward rendering after the light pre pass drawing is done.

    3)
    You can change mesh textures at runtime the same way that you would do it in any other XNA project.

    • Raziel Nomack says:

      Im new on 3D, im a Web Developer. But i have experience making Frameworks.
      I realy encourage U to make this a serious framework Of Light and Shadow. XNA dont have nothing like this. All 3d Engines on Codecomplex, have a lack of documentation and the code pattern are based on mind of developers resulting in a great learn curve.

      Look. U is the man. Dont overload your experiences with traing to make a hudge framework. Just resolve basic itens like alpha transparency, organize the code, for more easy learn and publish it. In App Hub forum, your exemples are very very commented. Im dont want to say what u need to do. Just give my opinion based on 2 weeaks of search, and there is nothing like this on the web. U are a pioner. Keep going.

      • I’m not the creator of this example, or the owner of this blog, but I’m sure J. Coluna knows who you meant🙂

        “Just resolve basic itens like alpha transparency”

        That doesn’t really make sense. J. Coluna’s sample code in this article is meant to demonstrate one method of drawing many lights in an efficient way. The downside to this method is that the lighting is done on a single “layer” of normals and depth values. So you can’t really have many transparent lit layers.

        I don’t think that the sample code in this article is trying to be a “framework for lighting and shadows”, instead he just hacked together a few lines of code, that demonstrate one particular way of lighting a scene.

        If you need to have transparent objects with lighting, then just draw using forward rendering instead.

        If you don’t know the difference between deferred and forward rendering, then perhaps google those terms and learn about them before trying to use this sample, or otherwise start with something simpler.

  18. Tom says:

    Hey! Great tutorial! I’ve got one question: how to strengthen or weaken the shadow color? I mean how to make shadow darker?

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