# How to set equivalent PDFs for cosine-weighted and uniform-sampled hemispheres?

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)
}

• Removing the π and using 1/2 and cos(theta) respectively appears to work. I wish I understood why, though. – hunterloftis Feb 11 at 9:05

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.

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 are not for extended discussion; this conversation has been moved to chat. – Dan Hulme Feb 13 at 22:28