<p>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:</p>
<pre><code>float x = cos(phi) * 2.0f * sqrt(r2);
float y = sin(phi) * 2.0f * sqrt(r2);
</code></pre>
<p>There should not be a factor of 2 in these: this is distorting the cosine distribution.</p>
<p>Also,</p>
<pre><code>vec3 pdf = dotp(uvw.w(), direction)/Pi;
throughput = throughput * brdf_result * cosine / pdf;
</code></pre>
<p>This isn't wrong, but it is unnecessary: the <code>pdf</code> cancels out the <code>cosine</code>, so it would be preferable to set <code>pdf</code> to just 1/π, and leave the cosine factor off. In fact, that also cancels the 1/π in the <code>brdf_result</code>, so you could leave off both of those pi factors and get rid of <code>pdf</code> entirely.</p>
<p>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.</p>
<p>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.</p>

CC BY-SA 4.0
2020-08-30T00:35:09.183Z
2020-08-30T00:35:09.183Z
Nathan Reed
<p>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:</p>
<p>1: the importance sampling one is correct!</p>
<p>2: I'm too stupid!!!!!!</p>

CC BY-SA 4.0
2020-08-24T12:34:51.123Z
2020-08-24T14:02:13.610Z
2020-08-24T14:02:13.610Z
dsukrect
<p>I was trying to do importance sampling on lambertian surface.At first I uniformly choose direction from unit sphere.</p>
<pre><code>vec3 direction = camera-&gt;genDirection();
...
direction = random_in_unit_sphere();
float cosine = dotp(direction,surfaceNormal);
/*
float dotp(float val){
val = dot(val);
if(val&gt;0.0001f) return val;
else return 0.0001f;
}
*/
vec3 brdf_result = material-&gt;baseColor/Pi;//lambertian
vec3 pdf = 1.0f/(2.0f*Pi);
throughput = throughput * brdf_result * cosine / pdf;
</code></pre>
<p>With 10 samples per pixel,yields:<a href="https://i.sstatic.net/PCAdE.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/PCAdE.jpg" alt="enter image description here" /></a></p>
<p>then I choose random direction from the unit hemisphere above the surface</p>
<pre><code>direction = random_in_unit_hemisphere(surfaceNormal);
float cosine = dotp(direction,surfaceNormal);
vec3 brdf_result = material-&gt;baseColor/Pi;
vec3 pdf = 1.0f/(1.0f*Pi);
throughput = throughput * brdf_result * cosine / pdf;
</code></pre>
<p>the result is very similar,except for less noise
<a href="https://i.sstatic.net/OEcgs.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/OEcgs.jpg" alt="enter image description here" /></a></p>
<p>and then I use the importance sampling method from (<a href="http://in1weekend.blogspot.com/" rel="nofollow noreferrer">http://in1weekend.blogspot.com/</a>)</p>
<pre><code>    class onb {
public:
    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&amp; a) { return a.x * u() + a.y * v() + a.z * w(); }
    void buildFromNormal(const vec3&amp; n) {
        axis[2] = normalize(n);
        vec3 a;
        if (std::abs(w().x) &gt; 0.9f)
            a = vec3(0.0f, 1.0f, 0.0f);
        else
            a = vec3(1.0f, 0.0f, 0.0f);
        axis[1] = normalize(cross(w(), a));
        axis[0] = cross(w(), v());
    }


private:
    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);
}
</code></pre>
<p>,</p>
<pre><code>onb uvw;
uvw.buildFromNormal(surfaceNormal);
direction = normalize(uvw.local(randCosDir()));
float cosine = dotp(direction,surfaceNormal);
vec3 brdf_result = material-&gt;baseColor/Pi;
vec3 pdf = dotp(uvw.w(), direction)/Pi;
throughput = throughput * brdf_result * cosine / pdf;
</code></pre>
<p>however the result is different:
<a href="https://i.sstatic.net/klRtz.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/klRtz.jpg" alt="enter image description here" /></a></p>
<p>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
<a href="http://in1weekend.blogspot.com/" rel="nofollow noreferrer">http://in1weekend.blogspot.com/</a> &quot;weekend one&quot; there is no cosine item at all.I really get messed with those concepts.Is there any one help me?thanks so much.</p>
<p>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).
<a href="https://i.sstatic.net/hWiHg.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/hWiHg.jpg" alt="enter image description here" /></a>
10,000 samples per pixel with uniform hemisphere sampling:
<a href="https://i.sstatic.net/CcPDQ.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/CcPDQ.jpg" alt="" /></a></p>

CC BY-SA 4.0
2020-08-24T07:37:09.770Z
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.
DirectX_Programmer
CC BY-SA 4.0
2020-08-24T10:45:45.460Z
@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(https://computergraphics.stackexchange.com/questions/4979/what-is-importance-sampling),but it was still,too bright,or maybe that was what lambertian actually looks like!
dsukrect
CC BY-SA 4.0
2020-08-24T11:00:49.587Z
@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.
DirectX_Programmer
CC BY-SA 4.0
2020-08-24T11:27:21.737Z
@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...
dsukrect
<pathtracing><importance-sampling>
issue with importance sampling
dsukrect
# issue with importance sampling

(Asked by: username_0 on 2020-08-24)

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](https://i.sstatic.net/PCAdE.jpg)](https://i.sstatic.net/PCAdE.jpg)

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](https://i.sstatic.net/OEcgs.jpg)](https://i.sstatic.net/OEcgs.jpg)

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

```
    class onb {
public:
    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);
        else
            a = vec3(1.0f, 0.0f, 0.0f);
        axis[1] = normalize(cross(w(), a));
        axis[0] = cross(w(), v());
    }

private:
    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;
uvw.buildFromNormal(surfaceNormal);
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](https://i.sstatic.net/klRtz.jpg)](https://i.sstatic.net/klRtz.jpg)

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](https://i.sstatic.net/hWiHg.jpg)](https://i.sstatic.net/hWiHg.jpg)
10,000 samples per pixel with uniform hemisphere sampling:
[![](https://i.sstatic.net/CcPDQ.jpg)](https://i.sstatic.net/CcPDQ.jpg)

**Tags:** pathtracing, importance-sampling

> **Comments:**
> **username_1**: 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.  
> **username_0**: @username_1,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(https://computergraphics.stackexchange.com/questions/4979/what-is-importance-sampling),but it was still,too bright,or maybe that was what lambertian actually looks like!  
> **username_1**: @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.  
> **username_0**: @username_1,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...  

---

## 2 answers

### Answer by username_2 on 2020-08-30. Score: 0 | Accepted answer

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.

Also,

```
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.


---

### Answer by username_0 on 2020-08-24. Score: 0

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!!!!!!


---

