XNA Light Pre-Pass: ambient light, SSAO and more

Hi folks,

I’ve added a few improvements to the LPP renderer, to make it shine: ambient lighting using cubemaps, SSAO, an example of a dual-layer shader and some other small changes. Here is a screenshot:

As usual, the full source code is available here. Use it at your own risk!

Ambient Light

Ambient light (or indirect light) is the light that doesn’t come necessarily from a single, direct light source: they are the light rays that bounce on the objects in the scene and ends up filling the darkness with a subtle lighting. It prevents the rendering to become black where there is no direct light. In real life, you can see it everywhere: even if you are under the shadow of a tree, you are able to see yourself because the light doesn’t come only directly from the sun: the clouds, the building walls, the floor, even the tree leaves are reflecting the light at you. There is a lot of ways to achieve that effect, from a simplistic approach of using a constant value across all objects, to more elaborated solutions like real-time global illumination.

On this sample I’m presenting 2 versions:

Constant ambient

This is the easiest way to achieve an ambient lighting: just add a constant term to the lighting equation. In the LPP, the final pixel color would be something like this:

Color = diffuse_texture * ( diffuse_lighting + constant_ambient) + specular_texture * specular_light;

As you can see, the scene is “flat”, and the lighting is constant across the whole scene.

Cubemap Lighting

As we can experience in real-life, the bounced light is not constant in all directions. We have some options: or we invent the most anticipated algorithm that creates the perfect global illumination solution for real-time games, or we hack it. I go with the second.

So the question is: how to store information about the lighting that surrounds an object? We could use spherical harmonics,  paraboloid mapping or a cubemap (or lightmaps, light grids, etc). I chose cubemaps for a few reasons: they are easy to visualize, to generate, to load and to bind to a material.

You can check this tutorial of how it works, but the basics is: a cubemap is used to store the lighting coming from all the directions. It can be seen as a box surrounding the object, where brighter areas means more light from that direction. Ambient light is a low-frequency data: to generate it we need first to get a cubemap with the original scene (your skybox is a good start) and convolute it (blur). This way, we will get rid of all details (high-frequency) and have only what matters. You will have some blue nuances where it used to be the sky, some orange tones where the Sun tints the horizon and so on. You can have multiple cubemaps on your scene, to best represent that section of the world: just capture a cubemap from a given point of view, use some tool to process it and in run-time choose the appropriate cubemap to be bound to the mesh. I use and recommend this ATI tool.

The shader now needs to fetch the correct pixel of the cubemap, either using the vertex normal or the pixel normal (I’m using the vertex normal in this example), and add to the lighting equation, that would look like this:

ambient = tex2d( ambient_cubemap, vertex_normal);
Color = diffuse_texture * ( diffuse_lighting + ambient) + specular_texture * specular_light;

As you can see, there are different shades on the character: his face has more light than his back. That’s because the skybox has a strong yellow Sun right in front of the character, and less intense tones in the back. Some bluish tones can be noted on his head too. The shader LPPMainEffect.fx has some defines to control what kind of ambient light you want. The ambient cubemap is modulated by an ambient color, so you can tweak the values per-mesh.

SSAO

Screen space ambient occlusion. I bet you’ve heard about it, so I will skip introductions. Here is the version without it. Notice that the character seems to float on the ground, since there is no direct shadows from his feet.

I tried a lot of different implementations, using only depth and depth+normals. I’ve ended up with the later, although I’m not happy with it: I’m pretty sure there is some good soul out there that can improve it and share the code back with me. I’m using a half-resolution depth buffer, and the SSAO texture is also half-res. I do some blur filtering, using the depth to avoid bleeding into the foreground, and you can notice a thin silhouette around the SSAO sometimes, mostly due to the downsampled buffers. There are lots of parameters to tweak, maybe you can find a setup that works great. I’m applying the SSAO map (that is like a black-white opacity texture) over the whole composition: if you prefer, you could use it to modulate only the ambient term, but I’m comfortable with the results I got.

The shader uses ideas and snippets from lots of different samples, so if you see some of your (or someone else) code being used, give me a shout and I’ll credit you (or remove the source).

The SSAO create some contact shadows when the feet are close to the ground, and also his arms projects some shadows on his chest.

Dedicated Specular Buffer

The Xbox port of XNA doesn’t provide a RGBA64 texture format. That means that if we use the HDRBlendable format we have only 2 bits for specular light (I used to store the specular lighting in the alpha channel). This is obviously not enough, so now at lighting stage, I render to two separate lighting buffers: a diffuse and a specular one. Another advantage is that we can have proper specular colors. It didn’t show up as a performance issue on Xbox, but I’d rather use a RGBA64 if available (maybe next XNA release?).

In the reconstruct shading stage, we need to remember to fetch both diffuse and specular light buffers, and use them accordingly.

Dual-layer

I’ve implemented a kind of multi-material shader, where you transition from one material to another according to the vertex color. In this case, I use also a pattern on the alpha channel of the main diffuse texture to mask/unmask the second layer. This way, we don’t have the smooth (and sometimes unnatural) blending of a default weighting, but another layer that reveals in interesting fashion. Look at the image below: I’ve drawn some “scratches” on the alpha channel of the main diffuse (the tiled floor texture), so the second layer (the gravel) shows up first as small scratche on the surface, and where the vertex color gets more intense, it replaces the first layer. All the settings to this material are exposed in the 3DMax shader that I also provide with the source, it’s just a matter of enabling some check boxes, selecting the textures and exporting the FBX.

Others

I’ve added a RenderWorld structure, where all the submeshes are placed. The Renderer does queries on this structure using the camera frustum or light volume, so it would be easy if you want to replace it with a KD-tree, quadtree or any structure you like.

Please take some time to watch me on youtube singing some cover songs, and be plagued with my brazilian accent =)

Here is also the video for my DreamBuildPlay entry:

That is it. See you next time!

-J.Coluna

About these ads

About jcoluna

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

21 Responses to XNA Light Pre-Pass: ambient light, SSAO and more

  1. Neil Knight says:

    The Ambient lighting is a really nice touch :o) Can these be baked at all?

    • jcoluna says:

      They could be baked into static geometry, but not on the characters. The generation of the cubemaps should be done offline, unless your target hardware has horsepower for that =)
      -J.Coluna

  2. Feedidy says:

    This gets better and better!
    I asked this last time, any ideas?

    I used to be able to change textures of meshes by doing
    Model.Meshes[“MeshName”].Effects[0].Parameters[“DiffuseMap”].SetValue(NewTexture);

    But now the models disappear off in to the renderer and I don’t know where to update them. Is there any way to change textures at runtime?

  3. Robot97 says:

    Coooool :D

  4. Nice Jorge, as good as it can be!

    I’m trying to reach you on skype, call me when you can.

  5. Feedidy says:

    ignore my post; as it turn’s out I’m retarded!
    :D

  6. Chris says:

    Nice finally seeing another update. You’re one of the people that has helped me get better at graphics rendering. I really want to try that SSAO code out!

  7. Jashua says:

    Coooool man….it’s Great…i test your code…but did u notice that point light and SSAO in your code does not match…

  8. Jashua says:

    ThanX man…I owe you.

  9. Pingback: Windows Client Developer Roundup 084 for 11/8/2011 - Pete Brown's 10rem.net

  10. Pingback: Skinned animations and shadows now in place « Electronic Meteor

  11. Nelxon says:

    I’ve been following your work for a while, I love the approach you are taking with your framework and pipeline. Smart, very smart. I added your site to my XDSK , http://nelxon.com/resources/xdsk2/
    Hopefully, it will send you a lot more visitors and maybe even more donations for motivation. :)

    Keep up the Great work.

  12. Preslav says:

    Hello, I’ve been following your blog for a while, and now when I got your last published sources and copied SSAO.fx into my project, I get a compilation error:

    Errors compiling ..\SSAO.fx:
    Compiled shader code uses too many instruction slots (594). Max. allowed by the target (ps_3_0) is 512.

    But of course your project compiles perfectly.
    Any idea why could this happen, maybe fxc builds it differently, optimizing yours but mine not?

  13. Marco says:

    I watched your trailer and I would ask you if you can also talk about topics like collision detection and how to manage character’s interaction with a game world like yours, or also how to do water…
    Good work!

    PS You’re very good with guitar :P;)

  14. Edgar says:

    Hi Jorge, nice work, I’m a big fan of your work. I’m trying to port my Light Propagation Volumes from XNA 3.1 to 4.0 but I can’t. Can you please help me? Thanks

  15. Hi, I saw the trailer and I’m wondering if you use the Cube Shadow Mapping technique to cast shadows of Point Light types? Thanks.

  16. Garold says:

    Great work mate!

    There were 2 minor errors in Camera.cs that prevented Xbox compilation. To fix, I assigned a value to size and topLeft at the start of the method.

    public void ProjectBoundingSphereOnScreen(BoundingSphere boundingSphere, out Vector2 topLeft, out Vector2 size)
    {
    size = Vector2.Zero;
    topLeft = Vector2.Zero;

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