PI or not to PI in game lighting equation

Version : 3.1 – Living blog – First version was 4 January 2012

With physically based rendering current trend of photo-realistic game, I feel the need to do my lighting equation more physically correct. A good start for this is to try to understand how our currently game lighting equation behave. For a long time, the presence or not in my game lighting equation of term \pi or \frac{1}{\pi} have been more based on trial and error than physics. The origin of these terms in game lighting equations have already been discussed by others [1][3][7][12]. But as I found this subject confusing I dedicated this post only to that topic. This post is not about code, it is about understanding what we do under the hood and why this is correct. I care about this because more correct game lighting equations mean consistency under different lighting condition, so less artists time spend to tweak values. I would like to thank Stephen Hill for all the help he provide me around this topic.

This post is written as a memo for myself and as a help to anyone which was confusing as I was. Any feedback are welcomed.

I will begin this post by talking about Lambertian surface and the specificity of game light’s intensity then talk about diffuse shading and conclude by specular shading. I will not define common term found in lighting field like BRDF, Lambertian surface… See [1] for all these definitions and notation of this post.

Origin of \pi term confusion

The true origin of the confusing \pi term come from the Lambertian BRDF which is the most used BRDF  in computer graphics. The Lambertian BRDF is a constant define as :


The notation f(l_c,v) mean BRDF parametized by light vector l_c and view vector v. The view vector is not used in the case of Lambertian BRDF. c_{diff} is what we commonly call diffuse color.
The first confusing \frac{1}{\pi} term appear in this formula. It come from a constraint a BRDF should respect which is name conservation of energy. It mean that the outgoing energy cannot be greater than the incoming energy. Or in other word that you can’t create light. The derivation of the \frac{1}{\pi} can be found in [3].

As you may note, game Lambertian BRDF  have not this \frac{1}{\pi} term. Let’s see a light affecting a Lambertian surface in game:

FinalColor = c_diff * c_light * dot(n, l)

To understand where the \frac{1}{\pi} disappeard, see how game light’s intensity is define. Games don’t use radiometric measure as the light’s intensity but use a more artist friendly measure  [1] :

For artist convenience, c_{light} does not correspond to a direct radiometric measure of the light’s intensity; it is specified as the color a white Lambertian surface would have
when illuminated by the light from a direction parallel to the surface normal (l_c=n)

Which mean, if  you setup a light in a game with color  c_{light} and point it directly on a diffuse only quad mapped with a white diffuse texture  you get the color of c_{light}.
Another way to see this definition is by taking the definition of a diffuse texture [2] :

How bright a surface is when lit by a 100% bright white light

Which mean, if  you setup a white light in a game with brightness 1 and point it directly on a diffuse only quad mapped with a diffuse texture, you get the color of the diffuse texture.
This is very convenient for artists which don’t need to care about physical meaning of light’s intensity unit.

Theses definitions allows to define the punctual light equation use in game. A punctual light is an infinite small light like directional, point or spot light common in games.

L_o(v)=\pi f(l_c,v)\bigotimes c_{light}\underline{(n\cdot l_c)}).

The derivation and the notation of this equation is given in [1]. L_o(v) is the resulting exit radiance in the direction of the view vector v which is what you will use as color for your screen pixel. v is not used for Lambertian BRDF.

Using the punctual light equation with a Lambertian BRDF give us :

L_o=\pi \frac{c_{diff}}{\pi} \bigotimes c_{light}\underline{(n\cdot l_c)}).

Which I will rewrite more simply by switching to a monochrome light (\bigotimes is for RGB) :

L_o=\frac{c_{diff}}{\pi} \pi c_{light} \underline{(n\cdot l_c)}

This shading equation looks familiar except the \pi term. In fact after simplification we get :

L_o=c_{diff} c_{light} \underline{(n\cdot l_c)}

Which is our common game diffuse lighting equation.

This mean that for artists convenience, the value they enter as brightness in light’s settings is in fact the result of the light brightness multiply by \frac{1}{\pi} (the energy conserving constant of Lambertian BRDF) . When artists put 1 in brightness, in reality they set a brightness of \pi. This is represented in the punctual lighting equation by \pi c_{light}. In this post, I will define as game light’s unit the fact of multiplying the light brightness by \pi and as game Lambert BRDF the c_{diff} term which is linked.

In the following I will describe the consequence of the game light’s unit on common diffuse lighting technic use in game then on common specular lighting technic.

Diffuse lighting in game

Lambert lighting

Read more of this post

Dive in SH buffer idea

Edit: I rewritte some part of this post to be more understandable. First version was 30 September 2011.

Deferred lighting/shading is common now day. Deferred lighting buffers store results of accumulate diffuse and specular lighting for a given view to be composite with material properties later. But is there other way to achieve the same goal ? Store lights information in buffer to light objects later in order to have more flexibility in the lighting process.
Obviously, this way has already been explored. An idea is to store lights information themselves in buffer [7], another is to store the lighting environment in a compact form to be decompressed later, as Steve Anichini and Jon Greenberg do with spherical harmonic buffer [1][2]. I have myself think about the idea of using a spherical harmonic buffer (SH Buffer) a long  time ago and finally decided to give it a try. As them, I will share my experience here in order to grow the discussion about this algorithm. Any feedbacks are welcome.

SH Lighting : Same approach different goal

When starting a new approach, the first step is to fix the context. From my understanding, in [1][2][3] the approach was to produce a low resolution buffer then upsampling it to composite with the scene.The low resolution buffer allow to minimize bandwidth used by SH buffers and the upsampling phase require a “smart” filter, like a bilateral filter or an ID buffer like in Inferred lighting [5]. The author seems to try to replace the classic deferred light approach with this SH deferred approach. My context is different. I am in a forward rendering context, one pass per light and have really heavy constraint on interpolator. Any extra lights require sending geometry again. The main purpose of the SH Buffer will be for fill light (secondary light), and so can tolerate more approximation.

SH buffer is an appealing approach :
– No need to have the normal (and I don’t have them)
– Can be offloaded on SPU/CPU on console
– Decoupled from geometry
– SH buffer can be composite in the main geometry pass and get access to all other material properties. Mean that complex BRDF are handled correctly (like the one I describe in my post Adopting a physically based shading model).

Requirement of SH buffers is frightening: Using quadratic SH (9 coefficient) is impractical in term of performance and memory, but linear SH (4 coefficient) give good result for simulating indirect light. So we used only 4 SH coefficients.
We need to store 4 coefficients for each channel R, G and B. This result to 3 * 4 float16 per pixel which mean 21Mo at 1280×720! And the composite in the main pass require to sample 3 float16 buffers and add several instructions inside the already heavy main shader.
Note that in my context, I don’t want to do smart upsampling of the SH buffer. First because of artifact introduced, second because I want to compose the SH buffers in the main pass to be able to apply complex BRDF  and the shader is heavy enough. I am ALU bound and will not suffer too much of the high-resolution compare to adding instructions.

Obviously we can’t afford this on console. I will describe the method I try in order to minimize these constraints in following section.


In this section we deal only with diffuse lighting.
Here is my reference scene with classic dynamic lights ( click on all images to see full-size):

On the left there 3 lights, green, red, blue. A yellowish light with large brightness in the middle and three other overlapping lights purple, green, blue on the right.
The stairs on left and right allow highlighting the problem of light directionality. On the left stair the green light affect the top of the step and the blue the side. On the right stair, the three lights affect the top of the step and the purple light the side.

Here is the step and details of my test.

The first step is to render lights in the SH buffer by accumulating them in linear SH. To be practical, we need to use RGBA8bit SH buffer instead of float16 buffer. This highlight one of the problem of SH coefficient: they are signed float and require a range higher than one to get HDR lighting.
To avoid precision issue and sign constraint, I draw every light affecting a pixel in a single draw call (so no additive blending). I generate several combinations of shaders for different number of lights and used the tiled deferred lighting approach (as describe in [8] [9] or [10]).  In deferred rendering context,  the tiled deferred approach is only a gain for many lights on the screen, if you have few lights it is not an optimization. However, in my case this is the only approach available as you need to have all the accumulated ligths in the shader. I limit myself to 8 lights for this testing. As you can see, all optimizations of deferred lighting/shading apply.

I used the Z buffer to recover world position in the SH buffer pass, apply attenuation and project each light in SH.

float4 SHEvalDirection(float3 Dir)
    float4 Result;
    Result.x = 0.282095;
    Result.y = -0.488603 * Dir.y;
    Result.z = 0.488603 * Dir.z;
    Result.w = -0.488603 * Dir.x;
    return Result;

void AddLightToSH(float4 InLightPosition, float4 InLightColor, float3 PixelPosition,
                  inout float4 SHLightr, inout float4 SHLightg, inout float4 SHLightb)
    // No shadow available for SH buffer lighting
    float3   WorldLightVectorUnormalized       = InLightPosition.xyz - PixelPosition;
    float    DistanceAttenuation               = Attenuation(WorldLightVectorUnormalized);
    float3   LightColorWithDistanceAttenuation = DistanceAttenuation * InLightColor;

    float4 SHLightResult                        = SHEvalDirection(normalize(WorldLightVectorUnormalized));

    SHLightr += SHLightResult * LightColorWithDistanceAttenuation.r;
    SHLightg += SHLightResult * LightColorWithDistanceAttenuation.g;
    SHLightb += SHLightResult * LightColorWithDistanceAttenuation.b;

cosine convolution of the SH method

SH is only an approximation of the lighting environment. The more coefficients you have, the more precision you get. With only 4 coefficients we lose a little intensity (having 9 coefficient will allow to be closest to the original intensity), but still get good result. SH coefficients for each channel are store in 3 RGBA8 buffer and require the use of multirendertarget (MRT). My way to compress them is as follows:

Read more of this post