This is an overview of my implementation of light pre-pass renderer (a.k.a. deferred lighting).

0. Rendering Setup

For every rendering frame, all objects  and lights are culled out using conventional view-frustum culling algorithm.  because the light pre-pass renderer cannot handle translucent objects properly, I also culled out alpha-blending objects. (They should be rendered separately.) Alpha-testing objects, on the other hand, are not translucent, and therefore should not be culled out. For lights, they are approximated with bounding volumes like spheres (for point lights) and cones (for spot lights) to be tested against the view-frustum.

1. Geometry Pass (object-space rendering)

– RenderTarget A8R8G8B8
– RenderStates z-test ON / z-write OFF / alpha-test ON / alpha-blend OFF

In this pass, all visible ‘opaque’ objects are sorted in front-to-back order, and rendered using a special pixel shader which writes out view-space normal and view-space depth value instead of object’s shaded color. Normal value is encoded into 2 components xy vector using the following codes, and then written to rg channel. Viewing depth value is linearly normalized by dividing it with far clipping distance of camera’s projection parameter, and then written to b channel.

Because this depth-normal output does not require objects’ color information, I needed no texture fetching operation (for objects’ diffuse textures) at first. For alpha-testing objects, however, I realized that, to have it correctly alpha-tested after pixel shader (and correct silhouette as a result), I had to write out objects’ alpha value to a channel.

// normal packing
float2 packedNormal =
         0.5f * ( normal.xy + float2( 1.0f, 1.0f ) );
packedNormal.x *= ( normal.z < 0 ? -1.0f : 1.0f );

2. Shadow Pass (object-space rendering)

Though any shadow mapping techniques can be used, I chose a VSM(variance shadow maps) technique for my implementation.

In this pass, objects that cast shadow are rendered with camera positioned at the each light’s position.

3. Occlusion Pass (screen-space rendering)

– Input Shadow Map Texture(s) / Depth-Normal Texture / Random noise texture (for SSAO)
– RenderTarget Single channel floating point (A32F) with half dimension

In this pass, for every pixels, shadow intensity values and ambient occlusion value are computed and written to the output (occlusion texture). For ambient occlusion, I used one of many SSAO implementations which exploits both normal and position (derived from view-depth). And, for each shadow map, I computed a shadow intensity value for each occluded pixels. Pixels that are farther from the light have lower intensities. And pixels that facing against the light’s direction also have lower intensities. All these shadow intensity values and ambient occlusion value are summed and clamped to [0, 1] before writing them to the output.

3.5. Occlusion Gaussian Blur Passes (screen-space rendering)

To minimize visual artifacts and unwanted noises of the occlusion texture, I added Gaussian blur rendering passes: a horizontal blur pass and a vertical blur pass.

4.  Lighting Pass (screen-space rendering)

– Input Depth-Normal Texture / Occlusion Texture
– RenderTarget 16-bit floating point (A16R16G16B16)
– RenderStates z-test OFF / z-write OFF / alpha-test OFF / alpha-blend ON / source-blend ONE / dest-blend ONE

In this pass, for each light, Phong shading coefficients are computed to be used at the following material pass. To maximize the rendering performance, I drew a full-screen quad polygon for every 74 lights and in pixel shader (unrolled) loop was used. (In my PS3.0 implmentation, compiled assembly code had about 1600 instructions.)

To keep it simple (and to avoid using multiple render targets), I used a single 4 channel render-target: rgb channel for diffuse term and  a channel for specular term that was computed as:

float specular = 
   pow( saturate( dot( N, H ) ), p )
   * lightAttenuation * max( 0, dot( N, L ) );

Before writing coefficients to the output, I simply multiplied the occlusion value (from the occlusion texture) to apply shadows and ambient occlusion effects.

5. Material Pass (object-space rendering)

– Input Light-Map Texture
– RenderTarget 16-bit floating point (A16R16G16B16)

In this pass, all visible ‘opaque’ objects are rendered again with slightly modified pixel shader, which performs Phong shading with the coefficients fetched from the light-map texture. That’s all. Lighting, shadowing, and ambient occlusion, they are all already computed in previous screen-space rendering passes.

6. Post Light Pre-Pass

After above rendering passes, alpha-blending objects and particle effects were rendered using conventional forward renderer using the same z-buffer that was used in the light pre-pass for correct visibility.

And then, screen-space post effects such as depth-of-field, bloom, and filmic tone-mapping were applied easily.