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

The most common diffuse lighting equation use in game is the classic Lambert lighting describe in the introduction which is the origin of the multiply by \pi of the light intensity. So for this case we already describe the impact: it allow to remove the \frac{1}{\pi} from the Lambertian BRDF.

Diffuse irradiance environment map

Lot of games choose to approximate diffuse distant lighting with irradiance environment map. For simplicity, I will use cubemap as mapping but other mapping like sphere, dual paraboloïd… work as well.

To better understand the impact of game light’s unit intensity with diffuse irradiance cubemap lighting we need to understand how to calculate an irradiance cubemap and what is the relationship between radiance and irradiance.
I won’t go into the complex definition of these terms. Most of what I write here is from [7]. To stay simple, radiance is a measure of light in a single ray and irradiance is a measure of light incoming to a surface point from all directions. The irradiance E under mathematic form is:

E = \int_\Omega L_i(l)cos(\theta_i)\mathrm{d}\omega_i

\Omega is the upper hemisphere of a surface so cosine is not clamped.

We will use a cubemap to represent the lighting environment (The function L_i(l) of the equation which take a direction in parameter and return radiance for this direction).
Texel of a cubemap represent radiance which are not in the game light’s unit we define in previous section :
– A “real” HDR light probe like the one provided by Paul Debevec [8] deal with real world unit.
– In-game engine generated HDR cubemap will contain only indirect lighting (in most common usage). The indirect lighting is the result of our game lighting bouncing on surface. Consequence, we don’t deal anymore with game light’s unit but with real world unit.
This result to :

E = \int_\Omega Cubemap(l)(n\cdot l)\mathrm{d}\omega_i

\mathrm{d}\omega_i is the solid angle subtended (cover) by cubemap texel designed by direction l.

This formula translates simply in pseudo code :

void GetIrradiance(n)
    E = 0
    foreach direction l
        if (dot(n,l) > 0)
            E += cubemap(l) * dot(n, l) * texelSolidAngle(l)

cubemap(l) return the value of the texel in the direction l.
texelSolidAngle(l) return the solid angle for the texel in direction l
The number of direction l is defined by the cubemap resolution.

So if we want to generate an irradiance cubemap, we can use the pseudo code:

for each texel of the destination cubemap 
    Get the direction n for this texel
         GetIrradiance(n) // Use the formula above

If you are interested the C++ code for these calculation can be found in source code of AMD Cubemapgen [10].
An irradiance cubemap store irradiance E for all direction (limited by the cubemap resolution).

Now that we know how to calculate irradiance cubemap, let’s see how to use it.
In case of Lambertian surface the exit radiance is proportional to the irradiance:


\frac{c_{diff}}{\pi} is the Lambertian BRDF. A derivation of this equation can be found in [7].
The irradiance cubemap need to be sampled by the Lambertian surface normal to retrieve irradiance E. Translated into code we get:

L_o = (c_diff / PI) * texCube(IrradianceCubemap, normal)

However for game we use c_{diff} for Lambertian BRDF due to our game light’s unit, so we would like to have:

L_o = c_diff * texCube(IrradianceCubemap, normal)

For this,we just have to turn irradiance into radiance at the irradiance cubemap generation time instead of inside the shader. Turning irradiance into radiance for Lambertian surface just mean dividing by \pi the irradiance E.

void GetIrradiance(n)
    E = 0
    foreach direction l
        if (dot(n,l) > 0)
            E += cubemap(l) * dot(n, l) * texelSolidAngle(l)
    E = E / PI

Diffuse irradiance environment map takeaway

The takeway here is to know what do your tool for generating irradiance cubemap. If the tools turn irradiance into radiance (divide irradiance cubemap by \pi), nothing special. If the tools don’t do it, you must apply the divide yourself when sampling the irradiance cubemap.

AMD Cubemapgen [10] and HDRShop [11] turn the irradiance to radiance when generating irradiance cubemap. To test it, you can input a grey (constant 0.5) HDR cubemap into the tool, generate the irradiance cubemap and check it is grey (constant 0.5) in output. I suppose that most tools do this to support LDR cubemap format like ARGB8 (In this case you can’t output a 0.5 * \pi irradiance cubemap, it will be clamped to 1.0).

Added note:

To generate an irradiance cubemap with AMD Cubemapgen, select the cosine filter with an angle of 180.
The following code extracted from the cubemapgen source is actually what perform the divide by \pi.

//divide through by weights if weight is non zero
if(weightAccum != 0.0f)
    for(k=0; k<m_NumChannels; k++)
         a_DstVal[k] = (float32)(dstAccum[k] / weightAccum);

Where weightAccum is the sum of dot(n, l) * texelSolidAngle of each texel.
To understand why, let’s calculate weightAccum:

WeightAcc = \int_\Omega cos(\theta_i)\mathrm{d}\omega_i = \pi

Derivation of this result can be found in [3].

Diffuse spherical harmonic lighting

Most game today used spherical harmonic (SH) as an approximation of the diffuse lighting environment. I will be lazy here and don’t introduce SH, I let reader refer to Stephen Hill’s post [4] for a quick sum up or Green talk for a large description [9] .

SH Irradiance map

The most know application of SH is the efficient evaluation of an irradiance environment map like the one of the previous section. Instead of creating an irradiance environment map you may keep it under the form of a more compact set of SH coefficients which I call SH irradiance map. The ShaderX2 article “Efficient Evaluation of Irradiance Environment Maps” of Peter-Pike Sloan [5] explain with source code how to generate and used these SH coefficients.

Remember the irradiance formula of the previous section

E = \int_\Omega L_i(l)cos(\theta_i)\mathrm{d}\omega_i

What we do here is convolving a lighting environment with the cosine lobe. This operation can be performed efficiently in a frequency space like allow SH with a simple dot product.
Then we use the relationship between irradiance and radiance for Lambertian surface as before to get our exit radiance:


Here is all the steps to do this with SH:
A. Generate SH irradiance map
– Project in SH the lighting environment (which I will represent by a cubemap and as said in the previous section a cubemap store radiance in real world unit)
– Project in SH the cosine lobe
– Convolve SH-projected lighting environment with SH-projected cosine lobe
B. Get exit radiance from irradiance
– Evaluate Irradiance for the normal direction in the irradiance cubemap represented by SH
– Turn irradiance to radiance for Lambertian surface by dividing by \pi and get exit radiance

The nice thing with the SH-projected cosine lobe convolution is that it sum up to three scale band factor (in the case of second order SH) : \hat{h}=[\pi,\frac{2\pi}{3},\frac{\pi}{4}]
Let’s see the pseudo-code for all these steps :

// A. generate SH irradiance map
// Project the lighting environment
for each texel of the cubemap
    float3 c_light = texel_radiance;
    float weight = texelSolidAngle;
    float3 n = texelDirection;
    float SHLightL[9];
    SHLightL[0] = 0.282095f * c_light * weight;
    SHLightL[1] = -0.488603f * n.y * c_light * weight;
    SHLightL[2] = 0.488603f * n.z * c_light * weight;
    SHLightL[3] = -0.488603f * n.x * c_light * weight;
    SHLightL[4] = 1.092548f * n.x * n.y * c_light * weight;
    SHLightL[5] = -1.092548f * n.y * n.z * c_light * weight;
    SHLightL[6] = 0.315392f * (3.0f * n.z * n.z - 1.0f) * c_light * weight;
    SHLightL[7] = -1.092548f * n.x * n.z * c_light * weight;
    SHLightL[8] = 0.546274f * (n.x * n.x - n.y * n.y) * c_light * weight;
// Convolve with SH-projected cosinus lobe
float ConvolveCosineLobeBandFactor[] =
    2.0f * PI/3.0f, 2.0f * PI/3.0f, 2.0f * PI/3.0f,
    PI/4.0f, PI/4.0f, PI/4.0f, PI/4.0f, PI/4.0f
for (int i = 0; i < 9; ++i)
    SHLightL[i] *= ConvolveCosineLobeBandFactor[i];
// B. Get exit radiance from irradiance
// Evaluate irradiance at surface with normal n
float SHLightResult[9];
SHLightResult[0] = 0.282095f * SHLightL[0];
SHLightResult[1] = -0.488603f * n.y * SHLightL[1];
SHLightResult[2] = 0.488603f * n.z * SHLightL[2];
SHLightResult[3] = -0.488603f * n.x * SHLightL[3];
SHLightResult[4] = 1.092548f * n.x * n.y * SHLightL[4];
SHLightResult[5] = -1.092548f * n.y * n.z * SHLightL[5];
SHLightResult[6] = 0.315392f * (3.0f * n.z * n.z - 1.0f) * SHLightL[6];
SHLightResult[7] = -1.092548f * n.x * n.z * SHLightL[7];
SHLightResult[8] = 0.546274f * (n.x * n.x - n.y * n.y) * SHLightL[8];
float result = 0.0f;
for (int i = 0; i < 9; ++i)
    result += SHLightResult[i];
// Turn irradiance to radiance for Lambertian surface and get exit radiance
L_o = result * c_diff / PI;

In order to have our game Lambertian BRDF c_{diff} at the end instead of \frac{c_{diff}}{\pi}, we apply the same simplification as previous section here: we will turn our irradiance to radiance at the SH irradiance environment generation. Most code, like the one in ShaderX2 will include the divide by \pi inside the SH-projected cosine lobe term resulting in scale band factor \hat{h}=[1,\frac{2}{3},\frac{1}{4}] .
Compacted code is now:

// A. generate SH irradiance map
// Project the lighting environment, convolve with cosine lobe, turn irradiance to radiance
for each texel of the cubemap
    SHLightL[0] = 0.282095f * c_light * weight;
    SHLightL[1] = -0.488603f * n.y * 2.0f/3.0f * c_light * weight;
    SHLightL[2] = 0.488603f * n.z * 2.0f/3.0f * c_light * weight;
    SHLightL[3] = -0.488603f * n.x * 2.0f/3.0f * c_light * weight;
    SHLightL[4] = 1.092548f * n.x * n.y * 1.0f/4.0f * c_light * weight;
    SHLightL[5] = -1.092548f * n.y * n.z * 1.0f/4.0f * c_light * weight;
// B. Get exit radiance from irradiance
// Evaluate irradiance (already turn to radiance) at surface with normal n
float SHLightResult[9];
SHLightResult[0] = 0.282095f * SHLightL[0];
SHLightResult[1] = -0.488603f * n.y * SHLightL[1];
for (int i = 0; i < 9; ++i)
    result += SHLightResult[i];
// Get exit radiance
L_o = result * c_diff;

Full C++ source code can be found in Modified AMD Cubemapgen [10] see the post AMD Cubemapgen for physically based rendering.
We get exactly the same behavior than for irradiance environment map, good.

Added note:
The convolution is tied to the surface property as we will see later, so it is better to apply scale band factor at the moment we know the surface to affect, so when evaluating irradiance (which is no more irradiance from a semantic point of view). Thank to Stephen Hill to point this (see the comment).
All the steps I describe is for better understanding, in practice most constant are precomputed together for efficiency and applied at different time. The point here is about semantic, not code.

SH Punctual lights approximation

Another common application of SH is to approximate diffuse lighting of multiple punctual lights. The principe are very similar to a SH irradiance map. But be aware here that we deal with punctual light with game light’s unit. The punctual light’s intensity need to be multiplied by \pi before to be projected in SH. This is the \pi c_{light} part of the punctual lighting equation.

The steps are exactly the same that for SH irradiance map so I won’t repeat them. The difference is that the light environment is no more a cubemap in real world unit, but a set of punctual light in game light’s unit.
Compacted code is now:

// A. generate SH irradiance map
// Project the lighting environment, convolve with cosine lobe, turn irradiance to radiance
for each punctual light
    float3 c_light = PI * c_punctual_light;
    float3 n = LightDirection;
    float SHLightL[9];
    SHLightL[0] = 0.282095f * c_light;
    SHLightL[1] = -0.488603f * n.y * 2.0f/3.0f * c_light;
    SHLightL[2] = 0.488603f * n.z * 2.0f/3.0f * c_light;
    SHLightL[3] = -0.488603f * n.x * 2.0f/3.0f * c_light;
    SHLightL[4] = 1.092548f * n.x * n.y * 1.0f/4.0f * c_light;
    SHLightL[5] = -1.092548f * n.y * n.z * 1.0f/4.0f * c_light;
// B. Get exit radiance from irradiance
// Evaluate irradiance (already turn to radiance) at surface with normal n
float SHLightResult[9];
SHLightResult[0] = 0.282095f * SHLightL[0];
SHLightResult[1] = -0.488603f * n.y * SHLightL[1];
for (int i = 0; i < 9; ++i)
    result += SHLightResult[i];
// Get exit radiance
L_o = result * c_diff;

Added note:
As for SH irradiance map, you may want to apply the convolution at the surface affecting time. See later.

Diffuse SH lighting takeaway

To use our game Lambertian BRDF (c_{diff}) with SH lighting:
– Think to turn irradiance into radiance by dividing the SH-Projected cosine lobe term by \pi.
To deals with our game light’s unit:
– When projecting cubemap into SH, nothing special todo
– When projecting punctual lights into SH, scale the light’s intensity by PI
So take care when mixing both cubemap and punctual lights into the same set of SH coefficients.

Care must be taken when artists do hand painted HDR cubemap! They should paint with real world unit in mind, rather difficult…

Specular lighting in game

For Lambertian BRDF, all is fine. But as I said in my post about Adopting a physically based shading model care must be taken when using an energy conserving specular BRDF.
For sample, with the classical normalized Phong term \frac{\alpha_p+1}{2\pi} the punctual light equation become:

L_o(v)=(\frac{c_{diff}}{\pi}\underline{(n\cdot l_c)}+\frac{\alpha_p+1}{2\pi}\underline{(r\cdot v)}^{\alpha_p}c_{spec})\pi \ c_{light}

Which simplify to

L_o(v)=({c_{diff}}\underline{(n\cdot l_c)}+\frac{\alpha_p+1}{2}\underline{(r\cdot v)}^{\alpha_p}c_{spec})\ c_{light}

So when dealing with an energy conserving specular term, don’t forget to divide the constant factor by \pi.

Wrap lighting in game

Wrap lighting is commonly use in game to fake subsurface scattering or area light. Wrap lighting change the cosine lobe formulation of Lambert law, it is not a physically based lighting model but it is helpful. This imply that we need to recalculate the energy conserving constant of our Lambertian BRDF because the \frac{1}{\pi} factor was calculated from the cosine not the wrap lighting equation. Formula of wrap lighting and derivation of the new energy conservation term for Lambert BRDF can be found in [12] :

WrapLighting = \frac{(n\cdot l)+\omega}{(1+\omega)}

with \omega between 0 and 1.
Energy conserving Lambertian BRDF with wrap lighting equation:

f(l_c,v)=\frac{c_{diff}}{\pi(1+\omega)}\frac{(n\cdot l)+\omega}{(1+\omega)}

Translated into code, our new diffuse lighting equation is:

FinalColor = ( c_diff / (PI * (1 + w)) ) * (dot(N, L) + w) / (1 + w);

Look at how it behave with our game light’s unit. As the change does not affect the original presence of the \frac {1}{\pi} term in the Lambertian BRDF itself, all advices for punctual lights with game light’s unit and environment map still apply with wrap lighting. We can use as game diffuse wrap lighting equation:

FinalColor = ( c_diff / (1 + w) ) * (dot(N, L) + w) / (1 + w);

Or simply as in [12]

FinalColor = c_diff * (dot(N, L) + w) / ((1 + w) * (1 + w));

The thing Stephen Hill highlight in comment and in a recent exchange I have with him is about the SH diffuse wrap lighting. The SH convolution we perform in this case is no more done with \hat{h}=[1,\frac{2}{3},\frac{1}{4}] but with \hat{g}=[1,\frac{(2-\omega)}{3},\frac{(1-\omega)^{2}}{4}] . See [4] for derivation of these values. Note that these values still include the divide by \pi which turn irradiance to radiance (which still valid with our new energy conserving constant). The important point here is that the convolution coefficient are tied to surface properties, and so should be evaluated in the shader. This will transform our steps for SH punctual lights as:

// A. generate SH irradiance map
// Project the lighting environment
for each punctual light
    float3 c_light = PI * c_punctual_light;
    float3 n = LightDirection;
    float SHLightL[9];
    SHLightL[0] = 0.282095f * c_light;
    SHLightL[1] = -0.488603f * n.y * c_light;
    SHLightL[2] = 0.488603f * n.z * c_light;
    SHLightL[3] = -0.488603f * n.x * c_light;
    SHLightL[4] = 1.092548f * n.x * n.y * c_light;
    SHLightL[5] = -1.092548f * n.y * n.z * c_light;
// B. Get exit radiance
// Evaluate SH at surface with normal n, perform the convolution with the wrap argument, turn irradiance to radiance
float SHLightResult[9];
SHLightResult[0] = 0.282095f * SHLightL[0];
SHLightResult[1] = -0.488603f * n.y * (2.0f-w)/3.0f * SHLightL[1];
SHLightResult[2] = 0.488603f * n.z * (2.0f-w)/3.0f *  SHLightL[2];
SHLightResult[3] = -0.488603f * n.x * (2.0f-w)/3.0f *  SHLightL[3];
SHLightResult[4] = 1.092548f * n.x * n.y * (1.0f-w) * (1.0f-w)/4.0f *  SHLightL[4];
SHLightResult[5] = -1.092548f * n.y * n.z * (1.0f-w) * (1.0f-w)/4.0f *  SHLightL[5];
for (int i = 0; i < 9; ++i)
    result += SHLightResult[i];
// Get exit radiance
L_o = result * c_diff;

This work nicely. So as you can see, we multiply by \pi when we projecting the light environment in SH to take count of our game light’s unit and we perform all surface dependent thing into the shader.


[1] Hoffman, “Crafting Physically Motivated Shading Models for Game Development” and “Background: Physically-Based Shading” http://renderwonk.com/publications/s2010-shading-course/
[2] Epic game, “UDK documentation” http://udn.epicgames.com/Three/TexturingGuidelines.html
[3] “Energy conservation in game”  http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
[4] Hill, “Righting wrap” http://blog.selfshadow.com/2011/12/31/righting-wrap-part-1/
[5] Sloan, “Efficient Evaluation of Irradiance Environment Maps” http://tog.acm.org/resources/shaderx/
[6] King, “Real-Time Computation of Dynamic Irradiance Environment Maps” http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter10.html
[7] Akenine-Möller, Haines, Hoffman, “Real-Time Rendering 3rd Edition” http://www.realtimerendering.com
[8] http://www.pauldebevec.com/
[9] Green, “Spherical Harmonic Lighting: The Gritty Details” http://www.research.scea.com/gdc2003/spherical-harmonic-lighting.pdf
[10] http://code.google.com/p/cubemapgen/
[11] http://www.hdrshop.com/
[12] McCauley, “Energy-Conserving Wrapped Diffuse” http://blog.stevemcauley.com/


17 Responses to PI or not to PI in game lighting equation

  1. Steve says:

    “So you must be aware that when you convert a punctual light with game light’s unit in SH coefficients you must use scale band factor […]”

    Here you’re really scaling the *punctual light* by pi, not changing the convolution, which is tied to the properties of the surface! Even if you roll SH light projection and convolution into one step in practice (because you’re doing this for diffuse surfaces only), I think it’s sensible to at least logically separate the two operations.

    From memory, this is how D3DX separates things. For instance, I believe D3DXSHEvalDirectionalLight will scale by ~pi (it’s not quite this because it’ll rescale slightly to ensure that “the resulting exit radiance of a point directly under the light on a diffuse object with an albedo of 1 would be 1.0”):

    • Steve says:

      I went ahead and checked D3DX and this code underscores the point I was making:

      #include <d3dx9.h>
      int main()
          // Same direction for light and normal
          D3DXVECTOR3 dir(0, 0, 1);
          // Lighting and surface orientation in SH
          float sh_l[9];
          float sh_n[9];
          D3DXSHEvalDirectionalLight(3, &dir, 1.0f, 1.0f, 1.0f, sh_l, NULL, NULL);
          D3DXSHEvalDirection(sh_n, 3, &dir);
          // Lambertian diffuse per-band convolution coeffs
          float sh_c[3] = {1.0f, 2.0f/3.0f, 1.0f/4.0f};
          // Evaluate exit radiance in direction of normal
          float c = 0.0f;
          int i = 0;
          for (int l =  0; l <  3; l++)
          for (int m = -l; m <= l; m++, i++)
              c += sh_c[l]*sh_n[i]*sh_l[i];

      [Seb: You may need to edit this comment, since I’m not sure I can use WP markup.]

      Where’s your \pi now? It’s part of D3DXSHEvalDirectionalLight. The result of c is 1.0, as promised by the documentation.

      This separation allows you to project different lighting independently, sum the results and then apply whatever convolution is appropriate for your surfaces at the end.

      • Steve says:

        “Evaluate exit radiance in direction of normal” is a poor choice of words, but you get the idea.

      • seblagarde says:

        Thank for the detailed explanation Steve.
        It’s been a long time since I use D3DX… 🙂

        All this is semantic because the code is the same but this is exactly why I write this post. Thank you for all this clarification, I will update the post to be more precise.

  2. seblagarde says:

    I rewritte complety this post to be more understandable and with the hint give by Steve + added a Wrap lighting section. The sentence in the comment of Steve refer to version 2.0 but all is remarks are very helpful!

  3. david says:

    “It mean that the outgoing energy cannot be greater than the outgoing energy” that is easy to see! 😉

  4. Pingback: Spherical Harmonics for Beginners | dickyjim

  5. Pingback: Extracting dominant light from Spherical Harmonics | 25cafe

  6. Hi Sebastien,
    its a bit late, but you can also find a valuable discussion about PI in BRDF here;


    I originally only a short side note about PI, but then somebody asked, and it got really detailed!

  7. Pingback: Light rendering on maps | mots d'un ingé

  8. Pingback: 从0开始pbr « Babylon Garden

  9. Pingback: Path Tracing – Getting Started With Diffuse and Emissive | The blog at the bottom of the sea

  10. Pingback: Randomly generated stuff

  11. Khayzo says:

    Hi Sebastien,

    Thanks for the blog post!

    I have a question about specular IBL and SSR: I believe that local reflection probes (used for specular IBL) should be treated like diffuse irradiance maps as being in “real world units” that needs to be converted into “game’s light unit” by dividing by PI during sampling. But what about SSR? I think it should be considered as being in “real world unit” as well and thus divided by PI but I would like your input on this please :).

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 )

Connecting to %s

%d bloggers like this: