<blockquote>
<p>For a ideal mirror that reflect without any energe loss,
<span class="math-container">$f(p,ω_o,ω_i)$</span>  is 1.</p>
</blockquote>
<p>I see your point, but unfortunately this is not true. If your material is an ideal mirror, your BRDF is proportionnal to a Dirac distribution, meaning that its integral is one, but all the reflectivity is contained in one single direction, that you note <span class="math-container">$\omega_j$</span>.</p>
<p>Therefore <span class="math-container">$f(p, \omega_i, \omega_o) \propto \delta(\omega_o - \omega_j)$</span>. You should notice that I didn't put an equal sign, this is because we want to be sure that your BRDF resends exactly the same amount of energy that it receives. We should check if it is the case:</p>
<p><span class="math-container">$$\int_{S^2} L_i(p,\omega_i) \ \delta(\omega_o-\omega_j) \ |\cos (\theta_o) | \ \text{d}\omega_o = L_i(p,\omega_i) \ |\cos (\theta_j)|$$</span>
By definition of a Dirac distribution. Therefore you should add in your BRDF a cosine value at the denominator to be sure to obtain <span class="math-container">$L_i(\omega_i)$</span> value as an output:</p>
<p><span class="math-container">$$f(p,\omega_o, \omega_i) = \frac{\delta(\omega_o-\omega_j)}{|cos(\theta_j)|}$$</span></p>
<p>Computing numerically Dirac distributions is complicated, it is better to simplify the rendering equation, that now simply gives:</p>
<p><span class="math-container">$$\int_{S^2} L_i(p,\omega_i) \ \frac{\delta(\omega_o-\omega_j)}{|cos(\theta_j)|} \ |\cos (\theta_o) | \ \text{d}\omega_o = L_i(p,\omega_i)$$</span></p>
<p>Your cosine is not gone, it just vanishes with another one at the denominator.</p>

CC BY-SA 4.0
2023-11-18T19:00:16.957Z
+1 this is correct. The specular reflection BRDF (and transmission BTDF) have a cosine term in them that cancels with the one in the rendering equation. See e.g. [§ 8.2.2 in PBRT 3](https://pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission#SpecularReflection). It's important to do that canceling, too, or you'll get numerical problems.
geometrian
CC BY-SA 4.0
2023-02-17T09:23:52.490Z
2023-02-17T09:23:52.490Z
Balfar
<p>Note that if <span class="math-container">$cos$</span> is not removed, the distribution function won't (necessarily) integrate to <span class="math-container">$1$</span> (energy is not conserved). Specular rays means that <span class="math-container">$f(...)$</span> is proportional to <span class="math-container">$C(\omega_r) \delta(\omega_i,\omega_r)$</span>, where <span class="math-container">$\delta$</span> is dirac's delta function and <span class="math-container">$\omega_r$</span> is the reflection direction. Lets put <span class="math-container">$L_i=1$</span> and integrate the distribution: <span class="math-container">$C(\omega_r)cos(\theta_r) = 1$</span> which implies that <span class="math-container">$cos(\theta_r)$</span> must be cancelled.</p>

CC BY-SA 4.0
2023-02-10T21:39:30.223Z
2023-02-10T21:39:30.223Z
Mathis
<p>Rendering equation's value can be estimated with Monte Carlo (<a href="https://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection" rel="nofollow noreferrer">Physically Based Rendering: Light Transport I: Surface Reflection</a>):</p>
<p><span class="math-container">$\begin{equation}
\begin{split}
L_o(p, \omega_o) &amp;= \int_{S^2}f(p, \omega_o, \omega_i) L_i(p, \omega_i) |\cos\theta_i| \mathrm{d}\omega_i\\
 &amp; = \frac{1}{N} \sum_{j=1}^{N} \frac{f(p, \omega_o, \omega_i) L_i(p, \omega_j) |\cos\theta_j|}{p(\omega_j)}
\end{split}
\end{equation}$</span></p>
<p>So if the hit surface is a mirror and <span class="math-container">$\omega_j$</span> is the correct direction (only 1 sample is needed and <span class="math-container">$p(\omega_j)=1$</span>) then this equation becomes:</p>
<p><span class="math-container">$\begin{equation}
\begin{split}
L_o(p, \omega_o) &amp; = f(p, \omega_o, \omega_i) L_i(p, \omega_j) |\cos\theta_j|
\end{split}
\end{equation}$</span></p>
<p>For a ideal mirror that reflect without any energe loss, <span class="math-container">$f(p, \omega_o, \omega_i)$</span> is 1.</p>
<p>Yet I couldn't find term <span class="math-container">$\cos\theta_j$</span> when reading code from Ray Tracing Weekend and smallpt.</p>
<p>In Ray Tracing Weekend, the <a href="https://github.com/RayTracing/raytracing.github.io/blob/master/src/TheRestOfYourLife/main.cc#L25" rel="nofollow noreferrer">ray tracing function</a> is written as</p>
<pre class="lang-cpp prettyprint-override"><code>color ray_color(
    const ray&amp; r,
    const color&amp; background,
    const hittable&amp; world,
    shared_ptr&lt;hittable&gt; lights,
    int depth
) {
    hit_record rec;

    // If we've exceeded the ray bounce limit, no more light is gathered.
    if (depth &lt;= 0)
        return color(0,0,0);

    // If the ray hits nothing, return the background color.
    if (!world.hit(r, 0.001, infinity, rec))
        return background;

    scatter_record srec;
    color emitted = rec.mat_ptr-&gt;emitted(r, rec, rec.u, rec.v, rec.p);

    if (!rec.mat_ptr-&gt;scatter(r, rec, srec))
        return emitted;

    if (srec.is_specular) {
        return srec.attenuation
             * ray_color(srec.specular_ray, background, world, lights, depth-1);
    }
    ...
</code></pre>
<p>The last part</p>
<pre><code>    if (srec.is_specular) {
        return srec.attenuation
             * ray_color(srec.specular_ray, background, world, lights, depth-1);
    }
</code></pre>
<p>implies that when hitting a specular object, the result (color) got attenuated by <code>srec.attenuation</code> but the cosine term is gone.</p>
<p>Similarly, neither <a href="https://www.kevinbeason.com/smallpt/" rel="nofollow noreferrer">smallpt</a> (line 61, 62) include cosine term when handling scatterring for specular object:</p>
<pre><code>else if (obj.refl == SPEC)            // Ideal SPECULAR reflection 
    return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi));
</code></pre>
<p>it shoots a ray to a new direction without having <span class="math-container">$\cos(\theta)$</span> after reflection.</p>
<p>So, how does <span class="math-container">$\cos(\theta)$</span> got cancelled for both renderer? Or did I misunderstand the equation?</p>

<raytracing><rendering>
cosine term in rendering equation
Rahn
# cosine term in rendering equation

(Asked by: username_0 on 2023-01-31)

Rendering equation's value can be estimated with Monte Carlo ([Physically Based Rendering: Light Transport I: Surface Reflection](https://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection)):

$\begin{equation}
\begin{split}
L\_o(p, \omega\_o) &= \int\_{S^2}f(p, \omega\_o, \omega\_i) L\_i(p, \omega\_i) |\cos\theta\_i| \mathrm{d}\omega\_i\\
& = \frac{1}{N} \sum\_{j=1}^{N} \frac{f(p, \omega\_o, \omega\_i) L\_i(p, \omega\_j) |\cos\theta\_j|}{p(\omega\_j)}
\end{split}
\end{equation}$

So if the hit surface is a mirror and $\omega\_j$ is the correct direction (only 1 sample is needed and $p(\omega\_j)=1$) then this equation becomes:

$\begin{equation}
\begin{split}
L\_o(p, \omega\_o) & = f(p, \omega\_o, \omega\_i) L\_i(p, \omega\_j) |\cos\theta\_j|
\end{split}
\end{equation}$

For a ideal mirror that reflect without any energe loss, $f(p, \omega\_o, \omega\_i)$ is 1.

Yet I couldn't find term $\cos\theta\_j$ when reading code from Ray Tracing Weekend and smallpt.

In Ray Tracing Weekend, the [ray tracing function](https://github.com/RayTracing/raytracing.github.io/blob/master/src/TheRestOfYourLife/main.cc#L25) is written as

```cpp
color ray_color(
    const ray& r,
    const color& background,
    const hittable& world,
    shared_ptr<hittable> lights,
    int depth
) {
    hit_record rec;

    // If we've exceeded the ray bounce limit, no more light is gathered.
    if (depth <= 0)
        return color(0,0,0);

    // If the ray hits nothing, return the background color.
    if (!world.hit(r, 0.001, infinity, rec))
        return background;

    scatter_record srec;
    color emitted = rec.mat_ptr->emitted(r, rec, rec.u, rec.v, rec.p);

    if (!rec.mat_ptr->scatter(r, rec, srec))
        return emitted;

    if (srec.is_specular) {
        return srec.attenuation
             * ray_color(srec.specular_ray, background, world, lights, depth-1);
    }
    ...

```

The last part

```
    if (srec.is_specular) {
        return srec.attenuation
             * ray_color(srec.specular_ray, background, world, lights, depth-1);
    }

```

implies that when hitting a specular object, the result (color) got attenuated by `srec.attenuation` but the cosine term is gone.

Similarly, neither [smallpt](https://www.kevinbeason.com/smallpt/) (line 61, 62) include cosine term when handling scatterring for specular object:

```
else if (obj.refl == SPEC)            // Ideal SPECULAR reflection 
    return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi));

```

it shoots a ray to a new direction without having $\cos(\theta)$ after reflection.

So, how does $\cos(\theta)$ got cancelled for both renderer? Or did I misunderstand the equation?

**Tags:** raytracing, rendering


---

## 2 answers

### Answer by username_1 on 2023-02-17. Score: 1

> For a ideal mirror that reflect without any energe loss,
> $f(p,ω\_o,ω\_i)$ is 1.

I see your point, but unfortunately this is not true. If your material is an ideal mirror, your BRDF is proportionnal to a Dirac distribution, meaning that its integral is one, but all the reflectivity is contained in one single direction, that you note $\omega\_j$.

Therefore $f(p, \omega\_i, \omega\_o) \propto \delta(\omega\_o - \omega\_j)$. You should notice that I didn't put an equal sign, this is because we want to be sure that your BRDF resends exactly the same amount of energy that it receives. We should check if it is the case:

$$\int\_{S^2} L\_i(p,\omega\_i) \ \delta(\omega\_o-\omega\_j) \ |\cos (\theta\_o) | \ \text{d}\omega\_o = L\_i(p,\omega\_i) \ |\cos (\theta\_j)|$$
By definition of a Dirac distribution. Therefore you should add in your BRDF a cosine value at the denominator to be sure to obtain $L\_i(\omega\_i)$ value as an output:

$$f(p,\omega\_o, \omega\_i) = \frac{\delta(\omega\_o-\omega\_j)}{|cos(\theta\_j)|}$$

Computing numerically Dirac distributions is complicated, it is better to simplify the rendering equation, that now simply gives:

$$\int\_{S^2} L\_i(p,\omega\_i) \ \frac{\delta(\omega\_o-\omega\_j)}{|cos(\theta\_j)|} \ |\cos (\theta\_o) | \ \text{d}\omega\_o = L\_i(p,\omega\_i)$$

Your cosine is not gone, it just vanishes with another one at the denominator.

> **Comments:**
> **username_2**: +1 this is correct. The specular reflection BRDF (and transmission BTDF) have a cosine term in them that cancels with the one in the rendering equation. See e.g. [§ 8.2.2 in PBRT 3](https://pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission#SpecularReflection). It's important to do that canceling, too, or you'll get numerical problems.  

---

### Answer by username_3 on 2023-02-10. Score: 0

Note that if $cos$ is not removed, the distribution function won't (necessarily) integrate to $1$ (energy is not conserved). Specular rays means that $f(...)$ is proportional to $C(\omega\_r) \delta(\omega\_i,\omega\_r)$, where $\delta$ is dirac's delta function and $\omega\_r$ is the reflection direction. Lets put $L\_i=1$ and integrate the distribution: $C(\omega\_r)cos(\theta\_r) = 1$ which implies that $cos(\theta\_r)$ must be cancelled.


---

