Sign in to follow this  
DarkChris

How to calculate Irradiance correctly?

Recommended Posts

Well, theres the integral in both the general Ambient Occlusion formula and the rendering equation. You have to use multiple samples to approximate this integral. But I can't think of any correct solution to do it.

You might want to just add all of the samples weighted by their cosine value:

irradiance += L(w) * dot(N, w);

But the more samples you use the brighter it gets. So I thought of dividing the sum by the amount of samples:

foreach (Sample w in Samples)
{
irradiance_sum += L(w) * dot(N, w);
}
irradiance = irradiance_sum / Samples.Count;

That kinda works, but I thought about it a bit more and found out that an infinite amount of samples and a L value of 1 for each sample, doesn't return 1 as result, but 2/pi.

Now maybe we have to divide it by this maximum value of 2/pi. But than the irradiance of just 1 sample turns out to be pi/2.

Can you please help me? An infinite amount of samples still needs to be able to return 100% of the incoming light, same for just 1 sample...

Share this post


Link to post
Share on other sites
Divide result by accumulated samples dotProduct.

edit: hmm my brains are slow.

foreach (sample in allSamples) {
acc += dot(sample.dir, normal)
irradiance += sample.color
}

irradiance /= acc;

/Tyrian

Ps: I`m bit tired, so double check is neaded!

Share this post


Link to post
Share on other sites
Ok, well this was one of the things I tried as well... I tried 2 versions of your solution....

Yours results in light being 57% brighter than the incoming light...

And a different version:

foreach(Sample w in Samples)
{
irradiance_sum += dot(N, w) * L(w);
cosine_sum += dot(N, w);
}
irradiance = irradiance_sum / cosine_sum;

That nearly works but doesn't for just 1 sample since it would be like that:
irradiance = dot(N, w) / dot(N, w) * L(w);

It just kills off the diffuse lighting completely...

Share this post


Link to post
Share on other sites
Well, this is basically the same as I've posted before, just multiplied by 2*pi... It still kills off the diffuse lighting with just 1 sample...

I believe the normalization factor must be something like that:

irradiance = (Samples.Count + 2) / (2*pi) * irradiance_sum;

This obviously isn't correct... But I've seen this normalization factor multiple times before for normalizing the specular highlights brightness... But maybe something like this would work here as well...

Also the paper you posted itself says that it's not totally right and only works with hi-res cube-maps, which is the same I said... pi / 2 * irradiance_sum / Samples.Count only works with an infinite amount of samples...

Quote:
but it tends to be a little off (particularly when using low resolution cube maps.)


[Edited by - DarkChris on September 29, 2010 1:41:05 PM]

Share this post


Link to post
Share on other sites
You have to weight each sample by the solid angle the sample represents. This is the area of the unit sphere subtended by the sample.

If your samples are uniform on the sphere, this is easy (2pi/numsamples). If you're sampling the sphere over lattitude/longitude a.k.a. theta/phi, the weight of each sample would be (2pi/xsamples)*(pi/ysamples)*sin(theta) or more simply sin(theta)*2*pi_squared/(numsamples)

If you're integrating over a hemisphere rather than a sphere, adjust by a factor of 2. If you want to normalize your irradiance to "lambertian unity" you have to divide by pi somewhere.

Share this post


Link to post
Share on other sites
Wow, that seems to be about right... Thank you and not only you but all of you for helping... I'll test this one out, but I'm quite sure that it's right, since sine actually is the integral of cosine... I somehow totally forgot that it might be useful here...

Share this post


Link to post
Share on other sites
Well, in an attempt to introduce the correct weights and explain a bit more:

You have to be aware that the weight of each sample depends on how you draw it.
If you weight your samples with solid angle you have to be careful that
1. They cover the whole (hemi)sphere
2. They don't overlap
(3. You associate the correct solid angle with each sample)
This may sound obvious but it is easy to get wrong in practice.

If you are integrating _uniformly_ spaced samples over the FULL unit sphere the weight is 4*pi/nSamples for each sample (for the HEMIsphere it is 2*pi/nSamples). (And here uniformly spaced means uniformly with respect to surface area.) You can verify your weights to some extent by integrating 1. The result should be the surface area of the sphere/hemisphere.

As irradiance makes only sense with respect to a surface (though maybe an imaginary one) I assume you want to integrate over the hemisphere. Also be aware that one sample in the case of irradiance is Li(wi)*dot(n,wi), i.e. the incident _radiance_ from direction wi times the cosine of the angle between the surface normal and incidient direction. Not just the radiance.

To get to the point, your basic loop looks like this:

E = 0;
foreach(direction wi)
{
E += Li(wi) * dot(n,wi) * weight;
}


For uniformly distributed samples over the hemisphere weight = 2*pi/nSamples.

If you are sampling in spherical coordinates with uniformly spaced theta and phi, and the spacings are dPhi = 2*pi/nPhi and dTheta = 0.5*pi/nTheta, then weight = sin(theta)*dTheta*dPhi.

sin(theta) comes from the fact that when you lay a grid over the sphere with theta and phi evenly spaced you get bigger patches at the 'equator' (theta=pi/2) than at the 'poles' (theta=0 or pi). And for infinitesimal dTheta and dPhi the area of one patch is sin(theta)*dTheta*dPhi (your weight). The sine is not related to the cosine in the integrant.

Share this post


Link to post
Share on other sites
"If you are integrating _uniformly_ spaced samples over the FULL unit sphere the weight is 4*pi/nSamples for each sample (for the HEMIsphere it is 2*pi/nSamples)"

quite right... my apologies

Share this post


Link to post
Share on other sites
Yeah, I forgot to mention that. Always calculate the irradiance using evenly spaced samples!

If you don't know how to generate evenly spaced sampling vectors, then this is what you need.
If you need sampling directions for the upper hemisphere then you can easily modify the algorithm presented in that page.

Share this post


Link to post
Share on other sites
Well thank you guys. I figured out a solution that would work pretty well. But since only an amount of 10 or less samples really differs from pi/2*radiance_sum/samples_count I chose to use an array that normalizes the irradiance based on the amount of samples. It works pretty well and is incredibly fast. There are situations were non uniformly placed samples are needed, but from what I've tried so far, it isn't noticeable.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this