For quite some time, I’ve been plagued by a question about Lambertian reflectors and sampling them in a renderer. The issue arises from the definition of a lambertian reflector. You might be familiar with the formulation of the Lambertian BRDF of \frac{Albedo}{pi}, this formulation is simple and constant across viewing direction and illumination direction. However, there is actually an implicit cosine factor based on viewing direction involved.

Lambertian surfaces emit less light as the viewing angle from the surface’s normal increases. The factor is actually cos(\theta_o) where \theta_o is the angle from our viewing direction to our normal. [1]

My question was, why don’t we apply this cosine factor when sampling our surfaces?

After all, we’re asking for how much light is being emitted from a surface at a given point.

If we wanted this answer, shouldn’t we also multiply it by the cosine of our viewing angle?

Shouldn’t it be \frac{Albedo}{\pi} * cos(\theta_o) * L_i * cos(\theta_i) instead of \frac{Albedo}{\pi} * L_i * cos(\theta_i)?

The problem lies in the mental model I’ve presented.

When rendering, we’re not simply asking how much light is being emitted at a given point.

We’re asking how much light is being emitted from a given area. This area, is the projection of our pixel onto our surface A_p.

If we imagine our rendering problem as a 2 dimensional problem – where our image plane is represented by a line segment and a pixel is represented as a segment of our line.

What we’re asking when rendering our surface, is not the light being emitted from a single sample point. But actually the light being emitted from the surface area of A_p.

EmittedLight = L_o * A_p Where L_o represents the amount of light being emitted in a particular direction per unit area.

As a result, if we do a bit of trigonometry, we can see that A_p = \frac{A}{cos(\theta_o)}.

And we also know that our Lambertian reflector emits light as L_o = \frac{Albedo}{\pi} * cos(\theta_o) * L_i * cos(\theta_i)

If we place these values in our equation for emitted light we get:

EmittedLight = L_o * A_p

EmittedLight = \frac{Albedo}{\pi} * cos(\theta_o) * L_i * cos(\theta_i) * \frac{A}{cos(\theta_o)}

EmittedLight = \frac{Albedo}{\pi} * L_i * cos(\theta_i) * A

Which cancels out our cosine terms! (A is implicit when rendering a pixel grid)

This led me to some interesting insights/questions.

  • The value represented by a pixel can be seen as radiant intensity.
  • The value calculated by our shader can be seen as radiant intensity. (If it was radiance, we would need to introduce the cosine term again since we would not be multiplying by projected area)
  • We can think of the pixel grid as a big integration of our image plane and thinking of the pixels as little squares can be a valuable tool.
  • Do all BRDFs have an implicit cosine term? This leads me to think that is the case.

That’s it for today! Thanks for popping by, hopefully you found this as insightful as I have.

[1] https://en.wikipedia.org/wiki/Lambert%27s_cosine_law