I was trying to do importance sampling on lambertian surface.At first I uniformly choose direction from unit sphere.

vec3 direction = camera->genDirection();
direction = random_in_unit_sphere();
float cosine = dotp(direction,surfaceNormal);
float dotp(float val){
val = dot(val);
if(val>0.0001f) return val;
else return 0.0001f;
vec3 brdf_result = material->baseColor/Pi;//lambertian
vec3 pdf = 1.0f/(2.0f*Pi);
throughput = throughput * brdf_result * cosine / pdf;

With 10 samples per pixel,yields:enter image description here

then I choose random direction from the unit hemisphere above the surface

direction = random_in_unit_hemisphere(surfaceNormal);
float cosine = dotp(direction,surfaceNormal);
vec3 brdf_result = material->baseColor/Pi;
vec3 pdf = 1.0f/(1.0f*Pi);
throughput = throughput * brdf_result * cosine / pdf;

the result is very similar,except for less noise enter image description here

and then I use the importance sampling method from (http://in1weekend.blogspot.com/)

    class onb {
    vec3 operator[](int i)const { return axis[i]; }
    vec3 u()const { return axis[0]; }
    vec3 v()const { return axis[1]; }
    vec3 w()const { return axis[2]; }
    vec3 local(float a, float b, float c) { return a * u() + b * v() + c * w(); }
    vec3 local(const vec3& a) { return a.x * u() + a.y * v() + a.z * w(); }
    void buildFromNormal(const vec3& n) {
        axis[2] = normalize(n);
        vec3 a;
        if (std::abs(w().x) > 0.9f)
            a = vec3(0.0f, 1.0f, 0.0f);
            a = vec3(1.0f, 0.0f, 0.0f);
        axis[1] = normalize(cross(w(), a));
        axis[0] = cross(w(), v());

    vec3 axis[3];
vec3 randCosDir() {
    float r1 = randFloat01();
    float r2 = randFloat01();
    float z = sqrt(1.0f - r2);
    float phi = 2.0f * Pi * r1;
    float x = cos(phi) * 2.0f * sqrt(r2);
    float y = sin(phi) * 2.0f * sqrt(r2);
    return vec3(x, y, z);


onb uvw;
direction = normalize(uvw.local(randCosDir()));
float cosine = dotp(direction,surfaceNormal);
vec3 brdf_result = material->baseColor/Pi;
vec3 pdf = dotp(uvw.w(), direction)/Pi;
throughput = throughput * brdf_result * cosine / pdf;

however the result is different: enter image description here

The baseColor of the wall is vec3(0.8f,0.8f,0.8f),and the color of the dome light is vec3(1.0f,1.0f,1.0f). In some tutorial the cosine item is inside the lambertian brdf,and some are in the render equation,and in http://in1weekend.blogspot.com/ "weekend one" there is no cosine item at all.I really get messed with those concepts.Is there any one help me?thanks so much.

another rendering with baseColor = vec3(1.0f,1.0f,1.0f) and dome color = vec3(0.5f,0.5f,0.5f) (importance sampling) the average color of the final image over all pixels is vec3(0.470884f,0.470884f,0.470884f). enter image description here 10,000 samples per pixel with uniform hemisphere sampling:

  • $\begingroup$ I'm also just learning raytracing at a moment, so it's only a suggestion to try - notice how in last code, your pdf is using dot to calculate its value. In previous cases you were using uniform pdf. I think that it might cause a faster convergence. Try to replace pdf in last bit of code with "vec3 pdf = 1.0f/(2.0f Pi);" or "vec3 pdf = 1.0f/(1.0f Pi);" and in first and second code snipet and compare results. $\endgroup$ – DirectX_Programmer Aug 24 '20 at 7:37
  • $\begingroup$ @DirectX_Programmer,Thanks for answering!I have replaced pdf back to "pdf = 1.0f/(1.0f*Pi)",that work,the image was back to the more realistic dark one,however it was still slightly different from the first one.I think the pdf item is to downweight the overweight part "uvw.local(randCosDir())",but it seems that the pdf didn't do the job very well,it downweight too much!I've try another importance sampling method from(computergraphics.stackexchange.com/questions/4979/… it was still,too bright,or maybe that was what lambertian actually looks like! $\endgroup$ – dsukrect Aug 24 '20 at 10:45
  • $\begingroup$ @dsurekt I guess that best solution would be to create raytracing algorithm accumulating over frames (for uniformly sampled raytracing), converging to ground truth over time and compare your single frame results with that. $\endgroup$ – DirectX_Programmer Aug 24 '20 at 11:00
  • $\begingroup$ @DirectX_Programmer,I've added the rendering with 10,000 samples per pixel(uniform hemisphere sampling)at the bottom of my question,It should be the correct lambertian look.I don't know why importance sampling will result in different image... $\endgroup$ – dsukrect Aug 24 '20 at 11:27

There are some bugs in your math. You found the problem with the 2π and 4π in the hemisphere and sphere sampling functions already, but also, these lines in the cosine hemisphere sampling are wrong:

float x = cos(phi) * 2.0f * sqrt(r2);
float y = sin(phi) * 2.0f * sqrt(r2);

There should not be a factor of 2 in these: this is distorting the cosine distribution.


vec3 pdf = dotp(uvw.w(), direction)/Pi;
throughput = throughput * brdf_result * cosine / pdf;

This isn't wrong, but it is unnecessary: the pdf cancels out the cosine, so it would be preferable to set pdf to just 1/π, and leave the cosine factor off. In fact, that also cancels the 1/π in the brdf_result, so you could leave off both of those pi factors and get rid of pdf entirely.

More about the cosine factor: the whole idea of sampling with a cosine-weighted hemisphere is to avoid needing to have the cosine factor in the path throughput. Basically you only want the cosine in one place: either in the sampling distribution or in the throughput, but not both. It's preferable to put it in the sampling distribution because then the variance in the samples is lower (as they don't have the strongly varying cosine factor in their throughput), so the rendering converges faster.

This is also a general maxim in path tracing: you generally want to move factors from the throughput into the ray distribution whenever practical. That's the idea of importance sampling of BRDFs (move factors from the BRDF into the ray distribution) and explicit light sampling (move parts of the incoming light distribution into the ray distribution) as well as more advanced things like multiple importance sampling or path guiding.


I just found that if I use pdf = 1.0f/(4.0f * Pi) in unit sphere sampling or pdf = 1.0f/(2.0f * Pi) in unit hemisphere sampling,the result are almost the same as the importance sampling one(I also get the same result when set the baseColor to vec3(0.4f,0.4f,0.4f),the half of vec3(0.8f,0.8f,0.8f). And the surface area of a unit sphere is just 4.0f * Pi(I forget why I use 2.0f * Pi before).I HAVE to come to two conclusions:

1: the importance sampling one is correct!

2: I'm too stupid!!!!!!

  • $\begingroup$ I'm happy to hear that. Please accept your answer to let know that question no longer requires attention. $\endgroup$ – DirectX_Programmer Aug 24 '20 at 16:35
  • 1
    $\begingroup$ I'll accept it tomorrow(StackExchange says that I can only accept it tomorrow or after.) $\endgroup$ – dsukrect Aug 24 '20 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.