# Environment map importance sampling (beginner)

beginner here.

I am implementing importance sampling for environment maps in Scotty3D, a CMU CS project (but I am not a student).

The task can be found here

I have a problem with the sample's pdf, it is unusually small and that results in the object being rendered white even if its albedo is not white.

Here is how I construct the CDF:

// Calculate Lsintheta across the image. Accumulate to normalize the pdf to 1 later.
for (size_t j = 0; j < h; j++) {
for (size_t i = 0; i < w; i++) {
// 0.5f at the pixel center. Else we never sample top and bottom rows
float Lsintheta = image.at(i, j).luma() * sin(PI_F*(j + 0.5f)/h);
total += Lsintheta;
cdf.push_back(total); // Joint Cdf. P(theta, phi)
}
}


So total would have all the pixels' luminosities. cdf would actually be the cumulative luminosity.

Next is the sampling and calculating the actual pdf.

float Xi = RNG::unit(); // [0, 1]
auto lower = std::lower_bound(cdf.begin(), cdf.end(), Xi*total); // cdf is not normalized yet. Scale the RV
auto index = std::distance(cdf.begin(), lower);
out_pdf = *lower;
if (index > 0) out_pdf -= cdf[index - 1]; // pdf[index] = cdf[index] - cdf[index - 1]
out_pdf /= total;


Xi is a uniformly sampled real number from 0 to 1. Then it is multiplied by the total luminosity in the image to get the sampled accumulated luminosity. Use std::lower_bound to get the nearest accumulated luminosity greater than or equal to it. Find the index of this nearest accumulated luminosity using std::distance. Then minus with the previous accumulated luminosity to get the actual luminosity in this pixel. Divide by total to get the pdf.

This doesn't work, but if I just do out_pdf = lower/total it can at least show the right color. What am I doing/understanding wrong?

• The code here looks fine. When you say it "doesn't work", what do you mean? What happens? Sep 13 at 18:47
• @NathanReed IIRC, it was the color being white regardless of the object's albedo. I think the pdf calculated was wrong (too small). Sep 14 at 6:25
• Hmm. Well, out_pdf = lower/total looks simply wrong AFAICT, as that is a CDF not a PDF. Peter made the point about having it sum up to 4π instead of 1, which would mean the pdf values were too small by about a factor of 12. Could that account for the errors? Sep 14 at 19:14

From all other implementations I have seen, you generally sample twice, once in the Y dimension to select a row and then once in the X dimension to selct the pixel from this row.

I am not sure if this is necissary, maybe you're one dimensional sampling is fine.

I think you also need to multiply your pdf so that it is relative to solid angle (so it's total sums to 4pi).

You should be able to do this by dividing by (2 * Pi * Pi * sinTheta).

PBRT is probably the best textbook to help you. You can find it online here:

http://www.pbr-book.org/

The source code is also available. Here is the code for the environment light:

https://github.com/mmp/pbrt-v3/blob/aaa552a4b9cbf9dccb71450f47b268e0ed6370e2/src/lights/infinite.cpp

and the code for the Distribution2D sampler:

https://github.com/mmp/pbrt-v3/blob/aaa552a4b9cbf9dccb71450f47b268e0ed6370e2/src/core/sampling.h

• yeah, just reading that textbook and implementing the same way helped. thanks. May 1 at 13:18