You are on page 1of 19

Sbastien Lagarde

Random thoughts about graphics in game

PI or not to PI in game lighting equation

JANUARY 8, 2012 9 COMMENTS (HTTP://SEBLAGARDE.WORDPRESS.COM/2012/01/08/PI-OR-NOT-TO-PI-IN-GAME-


LIGHTING-EQUATION/#COMMENTS)

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 or 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 lights 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 term confusion


The true origin of the confusing 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 mean BRDF parametized by light vector and view vector . The view vector is not used in the case of Lambertian
BRDF. is what we commonly call diffuse color.
The first confusing 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 cant create light. The
derivation of the can be found in [3].

As you may note, game Lambertian BRDF have not this term. Lets see a light affecting a Lambertian surface in game:

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

To understand where the disappeard, see how game lights intensity is define. Games dont use radiometric measure as the lights
intensity but use a more artist friendly measure [1] :

For artist convenience, does not correspond to a direct radiometric measure of the lights 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 ( )

Which mean, if you setup a light in a game with color and point it directly on a diffuse only quad mapped with a white diffuse
texture you get the color of .
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 dont need to care about physical meaning of lights 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.
.

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

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

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

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

Which is our common game diffuse lighting equation.

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

In the following I will describe the consequence of the game lights 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 of the light intensity. So for this case we already describe the impact: it allow to remove the 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 parabolod work as well.

To better understand the impact of game lights 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 wont 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 under mathematic
form is:

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

We will use a cubemap to represent the lighting environment (The function 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 lights 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 dont deal anymore with game lights unit but with real world unit.
This result to :

is the solid angle subtended (cover) by cubemap texel designed by direction .

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 for all direction (limited by the cubemap resolution).

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

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 . Translated into code we get:

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

However for game we use for Lambertian BRDF due to our game lights 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 the irradiance .

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 ), nothing special. If the tools dont 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 cant output a 0.5 * 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 .
//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, lets calculate weightAccum:

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 dont
introduce SH, I let reader refer to Stephen Hills 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

What we do here is convolving a lighting environment with the cosine lobe. This operation can be performed efficiently in a frequency
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 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) :

Lets 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[] =
{
PI,
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 at the end instead of , we apply the same simplification as previous section here:
In order to have our game Lambertian BRDF at the end instead of , 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 inside the SH-projected cosine lobe term resulting in scale band factor .
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
(http://seblagarde.wordpress.com/2011/09/08/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 lights unit. The punctual lights intensity need to be
multiplied by before to be projected in SH. This is the part of the punctual lighting equation.

The steps are exactly the same that for SH irradiance map so I wont 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 lights 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 ( ) with SH lighting:


To use our game Lambertian BRDF ( ) with SH lighting:
- Think to turn irradiance into radiance by dividing the SH-Projected cosine lobe term by .
To deals with our game lights unit:
- When projecting cubemap into SH, nothing special todo
- When projecting punctual lights into SH, scale the lights 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
(http://seblagarde.wordpress.com/2011/08/17/hello-world/) care must be taken when using an energy conserving specular BRDF.
For sample, with the classical normalized Phong term the punctual light equation become:

Which simplify to

So when dealing with an energy conserving specular term, dont forget to divide the constant factor by .

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
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 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] :

with between 0 and 1.


Energy conserving Lambertian BRDF with wrap lighting equation:

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 lights unit. As the change does not affect the original presence of the term in the Lambertian
BRDF itself, all advices for punctual lights with game lights 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 but with . See [4] for derivation of these
values. Note that these values still include the divide by 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
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 when we projecting the light environment in SH to take count of our game lights
unit and we perform all surface dependent thing into the shader.
Reference

[1] Hoffman, Crafting Physically Motivated Shading Models for Game Development and Background: Physically-Based Shading
http://renderwonk.com/publications/s2010-shading-course/
(http://renderwonk.com/publications/s2010-shading-course/)[2] Epic game, UDK documentation
http://udn.epicgames.com/Three/TexturingGuidelines.html
(http://renderwonk.com/publications/s2010-shading-course/)[3] Energy conservation in game
http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
(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/
(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/
(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
(http://tog.acm.org/resources/shaderx/)[7] Akenine-Mller, Haines, Hoffman, Real-Time Rendering 3rd Edition
http://www.realtimerendering.com
(http://www.realtimerendering.com)[8] http://www.pauldebevec.com/
(http://www.realtimerendering.com)[9] Green, Spherical Harmonic Lighting: The Gritty Details
http://www.research.scea.com/gdc2003/spherical-harmonic-lighting.pdf (http://www.research.scea.com/gdc2003/spherical-harmonic-
lighting.pdf)
[10] http://code.google.com/p/cubemapgen/ (http://code.google.com/p/cubemapgen/)
[11] http://www.hdrshop.com/ (http://www.hdrshop.com/)
[12] McCauley, Energy-Conserving Wrapped Diffuse http://blog.stevemcauley.com/ (http://blog.stevemcauley.com/)

FILED UNDER USE "ALL POSTS AND RECENT UPDATES" IN


About
THEthese
MENU adsTO
(http://en.wordpress.com/about-these-ads/)
NATIGATE THIS BLOG TAGGED WITH
BRDF, CUBEMAP, ENERGY CONSERVING, GAME LIGHTING, IRRADIANCE, LAMBERTIAN SURFACE, PI, SH, SPHERICAL
HARMONIC
9 Responses to PI or not to PI in game lighting equation

Steve says:
January 6, 2012 at 12:37 am
So you must be aware that when you convert a punctual light with game lights unit in SH coefficients you must use scale band
factor [...]

Here youre 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 youre doing this for diffuse surfaces only), I think
its 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 (its not
quite this because itll 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):
http://msdn.microsoft.com/en-us/library/windows/desktop/bb205449%28v=vs.85%29.aspx

Reply
Steve says:
January 6, 2012 at 7:02 pm
I went ahead and checked D3DX and this code underscores the point I was making:

1 #include <d3dx9.h>
2
3 int main()
4 {
5 // Same direction for light and normal
6 D3DXVECTOR3 dir(0, 0, 1);
7
8 // Lighting and surface orientation in SH
9 float sh_l[9];
10 float sh_n[9];
11 D3DXSHEvalDirectionalLight(3, &dir, 1.0f, 1.0f, 1.0f, sh_l, NULL, NULL);
12 D3DXSHEvalDirection(sh_n, 3, &dir);
13
14 // Lambertian diffuse per-band convolution coeffs
15 float sh_c[3] = {1.0f, 2.0f/3.0f, 1.0f/4.0f};
16
17 // Evaluate exit radiance in direction of normal
18 float c = 0.0f;
19 int i = 0;
20 for (int l = 0; l < 3; l++)
21 for (int m = -l; m <= l; m++, i++)
22 c += sh_c[l]*sh_n[i]*sh_l[i];
23 }

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

Wheres your now? Its 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.

Reply
Steve says:
January 6, 2012 at 7:07 pm
Evaluate exit radiance in direction of normal is a poor choice of words, but you get the idea.

seblagarde says:
January 6, 2012 at 7:36 pm
Thank for the detailed explanation Steve.
Its 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.

seblagarde says:
January 8, 2012 at 4:37 am
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!

Reply
david says:
August 14, 2012 at 8:36 pm
It mean that the outgoing energy cannot be greater than the outgoing energy that is easy to see!

Reply
seblagarde says:
August 14, 2012 at 8:42 pm
Aouch! Corrected, thank you

Reply
Pingback: Spherical Harmonics for Beginners | dickyjim

Pingback: Extracting dominant light from Spherical Harmonics | 25cafe

Blog at WordPress.com.

The Enterprise Theme.

You might also like