<p>You're simply not normalizing correctly, since you've picked the pdf for uniform to be <span class="math-container">$1$</span> which it is not, and for cosine to be <span class="math-container">$\cos\theta$</span> which it is not. The pdf for uniformly distributed points on the upper hemisphere is: <span class="math-container">$p_U(\theta, \phi) = \frac{\sin\theta}{2\pi}$</span>. The pdf for cosine distributed points on the upper hemisphere is: <span class="math-container">$p_C(\theta, \phi) = \frac{\cos\theta\sin\theta}{\pi}$</span>. The integrand of the rendering equation (formulated in terms of sampling the hemisphere and not the area formulation) is: <span class="math-container">$$brdf(\theta_i, \phi_i, x, \theta_o, \phi_o)L_i(x,\theta_i,\phi_i)\cos\theta_i\sin\theta_i$$</span>
Since the estimator is the integrand divided by the pdf, then in the case of uniform sampling the sine cancels and you get:
<span class="math-container">$$2\pi \cdot brdf(\theta_i, \phi_i, x, \theta_o, \phi_o)L_i(x,\theta_i,\phi_i)\cos\theta_i$$</span>
In the case of cosine sampling the sine and cosine cancel and you get:
<span class="math-container">$$\pi \cdot brdf(\theta_i, \phi_i, x, \theta_o, \phi_o)L_i(x,\theta_i,\phi_i)$$</span></p>

<p>Note that for a Lambertian material you should use cosine sampling since it produces lower variance.</p>

CC BY-SA 4.0
2019-02-13T22:28:37.243Z
Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackexchange.com/rooms/89698/discussion-on-answer-by-lightxbulb-how-to-set-equivalent-pdfs-for-cosine-weighte).
Dan Hulme
CC BY-SA 4.0
2019-02-11T10:26:07.327Z
2019-02-11T10:33:03.303Z
2019-02-11T10:33:03.303Z
lightxbulb
<p>So, for Uniform sampling the PDF is <span class="math-container">$1/2π$</span>, For Cos-weighted its <span class="math-container">$cos(θ)/π$</span>. The Lambertian BRDF has a <span class="math-container">$\pi$</span> term as well in the denominator for energy conservation.</p>

<p>When not optimizing things you should be dividing by <span class="math-container">$\pi$</span> during the BRDF calculation, then dividing by the proper PDFs mentioned above.</p>

<p>Considering all the factors into account, for uniform sampling you'd have</p>

<p><span class="math-container">$L_i*brdf*\cos(\theta) * \displaystyle{\frac{1}{pdf}}$</span></p>

<p><span class="math-container">$L_i * \frac{color}{\pi} * \cos(\theta) \displaystyle{\frac{1}{\frac{1}{2\pi}}}$</span></p>

<p><span class="math-container">$L_i * {color} * \cos(\theta) * 2$</span></p>

<p>Similarly for Cosine-weighted hemispherical sampling you'd have</p>

<p><span class="math-container">$L_i*brdf*\cos(\theta) * \displaystyle{\frac{1}{pdf}}$</span></p>

<p><span class="math-container">$L_i * \frac{color}{\pi} * \cos(\theta) \displaystyle{\frac{1}{\frac{\cos(\theta)}{pi}}}$</span></p>

<p><span class="math-container">$L_i * {color} $</span></p>

<p>Since you weren't dividing by <span class="math-container">$\pi$</span> in the BRDF, the division by the PDF should have been <span class="math-container">$1/2$</span> and <span class="math-container">$\cos(\theta)$</span> as you suggested but couldn't understand since the actual pdfs have a factor of <span class="math-container">$\pi$</span> in them.</p>

CC BY-SA 4.0
2019-02-11T18:26:34.420Z
2019-02-11T18:26:34.420Z
gallickgunner
<p>I'm trying to add BRDFs to a very basic path tracer. Starting out, I'd like to convert just the Lambertian material, with two different sampling methods, to ensure that everything is working right. Theoretically, a uniform sample and a cosine-weighted hemispherical sample should both render similar images with different amounts of noise.</p>

<p>However, I'm finding that the uniform sample yields a significantly darker image than the cosine-weighted sample. This makes sense as the uniform set will always be scaled by 1 whereas the weighted set will usually be scaled by more than one - but I don't know exactly how I should fix that. Maybe someone can enlighten me as to how these PDFs should work?</p>

<p>Here is the relevant material code (edited based on the most recent):</p>

<pre><code>func (l *Lambert) Scatter(_, n geom.Unit, uv, p geom.Vec, rnd *rand.Rand) (in geom.Unit, bsdf Color, pdf float64) {
    in, pdf = sampleUniform(n, rnd) // should be able to swap this out, right?
    bsdf = l.texture.Map(uv, p).Scaled(in.Dot(n))
    return
}

func sampleUniform(n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = n.RandInHemisphere(rnd)
    pdf = 0.5
    return
}

func sampleCosWeighted(n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = n.RandInHemisphereCos(rnd)
    pdf = n.Dot(sample)
    return
}

func sampleReflected(out, n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = reflect(out, n)
    pdf = 1
    return
}
</code></pre>

<p>And the relevant render loop code:</p>

<pre><code>func color(r Ray, s Surface, depth int, rnd *rand.Rand) Color {
    if depth &gt;= 50 {
        return black
    }
    hit := s.Hit(r, bias, math.MaxFloat64, rnd)
    if hit == nil {
        return black
    }
    out := r.Dir.Inv()
    emit := hit.Mat.Emit(hit.UV, hit.Pt)
    in, bsdf, pdf := hit.Mat.Scatter(out, hit.Norm, hit.UV, hit.Pt, rnd)
    if pdf &lt;= 0 {
        return emit
    }
    indirect := color(NewRay(hit.Pt, in, r.T), s, depth+1, rnd).Times(bsdf).Scaled(1 / pdf)
    return emit.Plus(indirect)
}
</code></pre>

CC BY-SA 4.0
2019-02-11T09:05:14.903Z
Removing the π and using 1/2 and cos(theta) respectively appears to work. I wish I understood why, though.
hunterloftis
<raytracing><pathtracing><brdf><physically-based>
How to set equivalent PDFs for cosine-weighted and uniform-sampled hemispheres?
hunterloftis
# How to set equivalent PDFs for cosine-weighted and uniform-sampled hemispheres?

(Asked by: username_0 on 2019-02-11)

I'm trying to add BRDFs to a very basic path tracer. Starting out, I'd like to convert just the Lambertian material, with two different sampling methods, to ensure that everything is working right. Theoretically, a uniform sample and a cosine-weighted hemispherical sample should both render similar images with different amounts of noise.

However, I'm finding that the uniform sample yields a significantly darker image than the cosine-weighted sample. This makes sense as the uniform set will always be scaled by 1 whereas the weighted set will usually be scaled by more than one - but I don't know exactly how I should fix that. Maybe someone can enlighten me as to how these PDFs should work?

Here is the relevant material code (edited based on the most recent):

```
func (l *Lambert) Scatter(_, n geom.Unit, uv, p geom.Vec, rnd *rand.Rand) (in geom.Unit, bsdf Color, pdf float64) {
    in, pdf = sampleUniform(n, rnd) // should be able to swap this out, right?
    bsdf = l.texture.Map(uv, p).Scaled(in.Dot(n))
    return
}

func sampleUniform(n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = n.RandInHemisphere(rnd)
    pdf = 0.5
    return
}

func sampleCosWeighted(n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = n.RandInHemisphereCos(rnd)
    pdf = n.Dot(sample)
    return
}

func sampleReflected(out, n geom.Unit, rnd *rand.Rand) (sample geom.Unit, pdf float64) {
    sample = reflect(out, n)
    pdf = 1
    return
}

```

And the relevant render loop code:

```
func color(r Ray, s Surface, depth int, rnd *rand.Rand) Color {
    if depth >= 50 {
        return black
    }
    hit := s.Hit(r, bias, math.MaxFloat64, rnd)
    if hit == nil {
        return black
    }
    out := r.Dir.Inv()
    emit := hit.Mat.Emit(hit.UV, hit.Pt)
    in, bsdf, pdf := hit.Mat.Scatter(out, hit.Norm, hit.UV, hit.Pt, rnd)
    if pdf <= 0 {
        return emit
    }
    indirect := color(NewRay(hit.Pt, in, r.T), s, depth+1, rnd).Times(bsdf).Scaled(1 / pdf)
    return emit.Plus(indirect)
}

```

**Tags:** raytracing, pathtracing, brdf, physically-based

> **Comments:**
> **username_0**: Removing the π and using 1/2 and cos(theta) respectively appears to work. I wish I understood why, though.  

---

## 2 answers

### Answer by username_3 on 2019-02-11. Score: 5 | Accepted answer

So, for Uniform sampling the PDF is $1/2π$, For Cos-weighted its $cos(θ)/π$. The Lambertian BRDF has a $\pi$ term as well in the denominator for energy conservation.

When not optimizing things you should be dividing by $\pi$ during the BRDF calculation, then dividing by the proper PDFs mentioned above.

Considering all the factors into account, for uniform sampling you'd have

$L\_i\*brdf\*\cos(\theta) \* \displaystyle{\frac{1}{pdf}}$

$L\_i \* \frac{color}{\pi} \* \cos(\theta) \displaystyle{\frac{1}{\frac{1}{2\pi}}}$

$L\_i \* {color} \* \cos(\theta) \* 2$

Similarly for Cosine-weighted hemispherical sampling you'd have

$L\_i\*brdf\*\cos(\theta) \* \displaystyle{\frac{1}{pdf}}$

$L\_i \* \frac{color}{\pi} \* \cos(\theta) \displaystyle{\frac{1}{\frac{\cos(\theta)}{pi}}}$

$L\_i \* {color} $

Since you weren't dividing by $\pi$ in the BRDF, the division by the PDF should have been $1/2$ and $\cos(\theta)$ as you suggested but couldn't understand since the actual pdfs have a factor of $\pi$ in them.


---

### Answer by username_1 on 2019-02-11. Score: 1

You're simply not normalizing correctly, since you've picked the pdf for uniform to be $1$ which it is not, and for cosine to be $\cos\theta$ which it is not. The pdf for uniformly distributed points on the upper hemisphere is: $p\_U(\theta, \phi) = \frac{\sin\theta}{2\pi}$. The pdf for cosine distributed points on the upper hemisphere is: $p\_C(\theta, \phi) = \frac{\cos\theta\sin\theta}{\pi}$. The integrand of the rendering equation (formulated in terms of sampling the hemisphere and not the area formulation) is: $$brdf(\theta\_i, \phi\_i, x, \theta\_o, \phi\_o)L\_i(x,\theta\_i,\phi\_i)\cos\theta\_i\sin\theta\_i$$
Since the estimator is the integrand divided by the pdf, then in the case of uniform sampling the sine cancels and you get:
$$2\pi \cdot brdf(\theta\_i, \phi\_i, x, \theta\_o, \phi\_o)L\_i(x,\theta\_i,\phi\_i)\cos\theta\_i$$
In the case of cosine sampling the sine and cosine cancel and you get:
$$\pi \cdot brdf(\theta\_i, \phi\_i, x, \theta\_o, \phi\_o)L\_i(x,\theta\_i,\phi\_i)$$

Note that for a Lambertian material you should use cosine sampling since it produces lower variance.

> **Comments:**
> **username_2**: Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackexchange.com/rooms/89698/discussion-on-answer-by-username_1-how-to-set-equivalent-pdfs-for-cosine-weighte).  

---

