About Me

Hi. I'm Josh Ols. Lead Graphics Developer for RUST LTD.


View Joshua Ols's profile on LinkedIn


Entries in IBL (7)


Lighting Volumes


Upon further reading of the documents, I noticed I made some mistakes. It looks like "Split/Second" just uses Lighting Volumes for diffuse Global Illumination. From what I can tell, their dynamic specular lighting is supplied by local lights, while the cubemap reflections are forward rendered for any objects that need metallic reflections.



To offer some clarification, a true Irradiance Volume would have an array of blurred environment cube maps for each 3D grid cell. These maps would represent rough-to-smooth specular lighting, which materials would sample via a gloss value. Lighting Volumes are an approximation of this, having only one extremely low-res cube map per grid cell to represent rough specular lighting.  So if I want my materials to have dynamic specular lighting consistent with the Lighting Volume's contribution, I must use a constant gloss value for all my materials.



Lighting Volumes[1] are basically 3D texture irradiance volumes, very similar to Crytek's Light Propogation Volumes[2]. As such, they carry many of the same benefits, including easy compatibility with deferred lighting. However, they are fundamentally different in that they are precomputed for offline storage, and use a variation of Valve's ambient cube basis rather than spherical harmonics. Nonetheless, they are apparently quite effective and have already shipped in a few titles, most notably F.E.A.R 2 and Split/Second[3].



[1] Gamefest 2010, Lighting Volumes

[2] SIGGRAPH 2009, Light Propogation Volumes in CryEngine 3

[3] SIGGRAPH 2009, Rendering Techniques in Split/Second



Despite my general apathy toward precomputed lighting, I am intrigued by this technique. Granted, it will never be comptetitive with lightmaps in terms of resolution, lighting quality, etc, but that isn't the point. My deferred lighting renderer already handles high-resolution direct lighting well enough, and doesn't need any assistance in that area. Instead, these will handle high-quality ambient lighting, a task which deferred lighting is not well-suited to handle.


Now this system does raise a few concerns, such as how I should handle specular lighting. As I understand it, F.E.A.R.2 achieved a glossy specular by just reusing the volume data with a reflection vector rather than a surface normal. Whereas Split/Second appears to be using a separate cubemap, so that they can get high-res metallic reflections. Since each approach has different merits, I will need to try them both to see which one will best meet my needs.


Of course this system also raises questions of what else I can do with the volume data, which warrant further investigation. For example, I can use the volume to provide a reasonable source of environment lighting for translucent objects/particles. I'm also wondering if I should take it as far as Crytek, and try to do colored fog using the volume. Needless to say, there are certainly plenty of fun possibilities with this system that demand to be explored.


Final Thoughts:

One thing that is likely to be a problem is that, much like most other forms of IBL, I need to use world-space inputs when sampling the data from one of these volumes. So with my current renderer that means I not only have to unpack my normals, but also transform both the normals and the recovered positions from view-space to world-space. Something that will likely be a fullscreen pass, since Lighting Volumes will be needed for each part of the scene.


Yet another reason why I am strongly considering dumping packed normals + gloss in favor of raw world-space normals.


Self-Shadowed Bump maps

Something I thought I should mention that I realized only recently. In the section "Usage tips", I realized I was in error by saying that you need to swap the green & blue channels to make it work with RH normals. It is actually much simpler to swap the bottom two vectors in the basis in the shader to get the same effect at no extra cost. This way, you can use readily available tools, without any need to modify their output and introduce another step in the content creation pipeline.

Other than that, forget to mention the bonus pictures I added to the end of the post. Check them out, if you haven't already. ;)

SashaRX was asking about reflection vectors from an SS Bump map, so I thought I'd add it up here where everyone can see. Basically, all you need to do is get a tangent-space normal back, and then apply standard techniques from there. This is achieved by applying an inverse transformation to the SS Bump value using the basis vectors. Using just the basis vectors, the result will be in tangent-space. If you concatenate the tangent2world matrix with the basis vectors, you can transform staight to world-space.

Hope that helps. ;)

tanNormal = bumpBasis[0] * bump.x + bumpBasis[1] * bump.y + bumpBasis[2] * bump.z;
tanNormal = normalize(tanNormal);


For those who don't know, Self-Shadowed Bump maps were originally developed by Valve for use with their Radiosity Normal Mapping technology. After I decided to make the switch to a renderer built around pre-computed data, this was one of the first things I decided to try out. If you wish to read more about the technique, the following links have provided me with most of my information on the subject.


[1] Efficient Self-Shadowed Radiosity Normal Mapping

[2] Surface Detail Maps with Soft Self-Shadowing

[3] Half-Life 2 / Source Shading

[4] Shading in Valve's Source Engine

[5] SSBump Generator (used to make the maps)



During the course of my experiments, I have verified all the benefits that Valve described in their papers. However, I have also discovered a few drawbacks that they didn't mention. Keeping that in mind, I have pooled and summarized my findings from all the papers and my own experiments.


  1. Directional Occlusion

  2. Correct filtering

  3. Detail mapping

  4. Recovers bent normals

  5. Better use of byte precision

  6. Faster than tangent-space normal maps

    • NOTE: Only for RNM, not for general diffuse lighting


  1. Diffuse Lighting expense

    • In order to get dynamic lighting with directional occlusion, each light direction has to be projected onto the basis. This boils down to the same amount of math as a 3x3 matrix multiply, per light direction.

  2. Poor compression

    • DXTC causes artifacts at detail edges, and nasty blocky artifacts in specular/reflection

    • RGB5 is acceptable for diffuse lighting, but produces banding for specular/reflections. Also, this format is not supported by Direct3D 10/Xbox 360.



height normalMapRH bumpRH ss bumpRH (SS Bump) recovered tangent (SS Bump) no DO SS Bump (bumped) (SS Bump) reflection

Figure 1. 1, Height map; 2, Normal map; 3, SS Bump (no DO); 4, SS Bump (DO); 5, recovered tangent normals; 6, no DO; 7, DO; 8, reflection



(10) Red,  Right (11) Green, Upper Left (12) Blue, Lower Left (13) Right (14) Upper Left (15) Lower Left (16) Right (17) Upper Left (18) Lower Left

Figure 2. 1-3, RGB channels; 4-6, lightmaps w/o masks; 7-9, lightmaps masked

I added these images to help visualize how these maps just store multiplicative masks for the lightmaps. Note how sides facing the light direction receive almost no darkening, while those that are occluded are darkened to give the impression of shadowing.


Detail Mapping:

(SS Bump) enhanced detail (SS Bump) detailed (9) Recovered normal 17 - VTF 2010-02-24 01-46-50-17

Figure 3. 1, SS Bump map; 2, Detail map; 3, combined with SS Bump; 4, recovered normal; 5, diffuse lighting

This trick works since an SS Bump map is just a series of masks that get multiplied with the lightmaps. Since the lightmap data will be multiplied with the diffuse texture, we can effectively multiply the diffuse texture by the detail map if we just apply the detail map evenly to each color channel in the SS Bump map. This even weighting of each channel is what ensures that the recovered tangent normals are unaffected by the detail map.


Compression Artifacts:

(SS Bump) DXT1 (SS Bump) DXT1 diffuse (SS Bump) DXT1 reflection

Figure 4. 1, DXT1 SS Bump map; 2, diffuse lighting; 3, reflection

(SS Bump) RGB5 diffuse (SS Bump) RGB5 reflection

Figure 5. 1, RGB5 diffuse lighting; 2, reflection


Usage Tips:

Most tools that generate SS Bump maps will format them for the Source Engine, so they assume a left-handed coordinate system. This will cause issues with any programs that use a right-handed coordinate system. Fortunately, this issue can easily be fixed in a decent image editing program (Photoshop, Gimp, etc).

Tangent-Space normals:

Here, you just need to invert the contents of the green channel to flip the Y-axis.

normalMapLH normalMapRH

Figure 6. 1, normal LH; 2, normal RH


Self-Shadowed Bump maps:

Here, you have to swap the contents of the green and blue channels.

SS BumpLH ss bumpRH

Figure 7. 1, SS Bump LH; 2, SS Bump RH


Other than that, I thought I'd mention a problem I encountered while writing shader code to use these maps. The order of the bump basis vectors as Valve listed them in the paper can be a bit confusing, and will produce incorrect results that will have you scratching your head for days. To save you that trouble, here is how they should appear:


static half3 bumpBasis[3] =
    half3(sqrt(2.0) / sqrt(3.0),              0.0, 1.0 / sqrt(3.0)),
    half3(     -1.0 / sqrt(6.0),  1.0 / sqrt(2.0), 1.0 / sqrt(3.0)),
    half3(     -1.0 / sqrt(6.0), -1.0 / sqrt(2.0), 1.0 / sqrt(3.0))


Final Thoughts:

Despite the added storage and lighting cost, the benefits from using this technique are too enticing for me to pass up. So I have decided to integrate this technology into my new renderer for handling static objects. Dynamic objects, or anything that needs detail normal mapping, will still use Partial Derivative Normal maps for efficiency & flexibility. Between these two solutions, I should have a sufficiently versatile system for generating pleasing visuals.



Something I should have added a long time ago, there are two screenshots that have been sitting the in the comments section for some time now. They use a, SS Bump map from Valve's wiki, and really shows just how much depth this technology can add to even a flat surface. Check them out! ;)

(SS Bump) detailed 1(SS Bump) detailed 2

SS Bump map from Valve's wiki, "$ssbump"

Figure 8. 1, 0 degrees; 2, 90 degrees

Page 1 2