<p>I am not sure whether this is the problem, but I do find something that does not make sense and can be improved. If I understand it correctly, what you are doing is:</p>
<ul>
<li>First sample an emissive triangle in a two-step way: (1) uniformly choose an emissive triangle from the buffer (therefore, the number of triangles <strong>should</strong> be accounted for as PDF) (2) uniformly sample a point on that triangle. Convert the emitter PDF to solid angle measure.</li>
<li>Evaluate the PDF of BSDF sampling (in the same NEE direction) -&gt; get PDF in solid angle measure.</li>
<li>MIS, for emitter sampling.</li>
<li>(Here starts what confuses me) BSDF sampling, trace a new ray (therefore, not an one-sample-model MIS). <strong>If it hits the exact same triangle sampled in the emitter sample step</strong>: evaluate emitter PDF and then perform MIS.</li>
</ul>
<p>The part &quot;If it hits the exact same triangle sampled in the emitter sample step&quot; doesn't make sense:</p>
<ul>
<li>We know that the sampling event of the emissive triangle and BSDF sampling in the second step is independent: obviously, BSDF sampling itself in the second step has no dependency over which emissive triangle you sample. Therefore, let <span class="math-container">$\omega$</span> be the direction sampled via BSDF sampling, the PDF of BSDF samples a direction <span class="math-container">$\omega$</span> <strong>and the ray in the same direction hits the sampled emissive triangle</strong> should be:</li>
</ul>
<p><span class="math-container">$$
p_{BSDF}(\omega) \times p_{\text{hit}}(i)
$$</span></p>
<p>This is to say that ray with direction <span class="math-container">$\omega$</span> hitting the same emissive triangle is actually conditioned on the your sampling result, and I guess <code>brdf_with_mis_contribution</code> should be small, since it is only divided by <code>direction_pdf</code> and previously we will multiply both <code>brdf_with_mis_contribution</code> and <code>light_with_mis_contribution</code> by number of light primitives yet when you remove this multiplier, <code>brdf_with_mis_contribution</code> is far less than <code>light_with_mis_contribution</code>. This also changes your MIS weight in emitter sampling step.</p>
<p>I can't be exactly sure what <span class="math-container">$p_{\text{hit}}(i)$</span> should be, and I think it would be the solid angle subtended by the sampled emissive triangle at the closest hit point, divided by <span class="math-container">$4\pi$</span>.</p>
<p>I am not sure whether this is the case, but anyway here is my personal suggestion: you can refer to <a href="https://github.com/mitsuba-renderer/mitsuba2/blob/master/src/integrators/path.cpp" rel="nofollow noreferrer">mitsuba2/path.cpp</a> and check how they implement emitter MIS. Basically, BSDF sampling step is a bit wasteful since you only trace this ray to test whether it hits the emitter and if it doesn't, the ray is discarded. Mitsuba2 does a clever move: this BSDF sample is reused until the next ray intersection: if an emitter is hit, I compute the BSDF MIS weight for emission, while if a non-emissive triangle is hit, we can just continue the ray tracing and consider this BSDF sample as recursive ray update. This way, you can correctly account for BSDF sampling and don't need to further consider the case where an emitter is hit directly during path tracing.</p>

CC BY-SA 4.0
2024-06-25T16:06:03.307Z
What is the "one-sample-model MIS"?
Tom Clabault
CC BY-SA 4.0
2024-06-25T16:22:05.773Z
Check [Veach Chapter 9 MIS: 9.2.4  the one-sample model](https://graphics.stanford.edu/courses/cs348b-03/papers/veach-chapter9.pdf).
Enigmatisms
CC BY-SA 4.0
2024-12-13T07:40:43.763Z
> Mitsuba2 does a clever move: this BSDF sample is reused until the next ray intersection:

A few months after, I realize that this is a smart optimization. Are there maybe other optimizations like this one (in Mitsuba or not) in the wild? i.e. not so common but good optimizations like this?
Tom Clabault
CC BY-SA 4.0
2024-12-13T14:28:14.633Z
For this trick, AFAIK, Falcor has it (check [this](https://github.com/NVIDIAGameWorks/Falcor/blob/master/Source/RenderPasses/PathTracer/PathTracer.slang) out, line 891-921), yet Tungsten and pbrt-v3 do not implement this trick. For 'simple but smart' optimizations, the only one I can recall is the RR in the ratio tracking estimator, truly simple but smart: [Falcor GridVolumeSampler.slang, line 245-279](https://github.com/NVIDIAGameWorks/Falcor/blob/master/Source/Falcor/Rendering/Volumes/GridVolumeSampler.slang).
Enigmatisms
CC BY-SA 4.0
2024-06-25T04:19:18.680Z
2024-06-25T04:19:18.680Z
Enigmatisms
<p>I'm trying to implement multiple importance sampling for direct lighting estimation but I'm having a hard time understanding why adding the number of triangles to the PDF of my light sample yields very dark results:</p>
<p>Below is the code I found mostly by trials and errors which I assume gives correct results:</p>
<pre><code>// Sample light with MIS
Light sampled_light = sample_random_point_on_lights(...);

light_sample_pdf = 1.0f / sampled_light.area()
light_sample_pdf *= distance_to_light * distance_to_light;
light_sample_pdf /= dot_light_source;

float brdf_pdf;
ColorRGB brdf = brdf(..., &amp;brdf_pdf);

float mis_weight = power_heuristic(light_sample_pdf, brdf_pdf);

ColorRGB Li = sampled_light .emission;
float cosine_term = dot(shading_normal, shadow_ray.direction);

ColorRGB light_with_mis_contribution = Li * cosine_term * brdf * mis_weight / light_sample_pdf;





// Sample BRDF with MIS
float sampled_pdf;
Vector brdf_sampled_direction;
ColorRGB brdf = brdf_sample(..., &amp;sampled_pdf, &amp;brdf_sampled_direction);

ColorRGB brdf_with_mis_contribution(0.0f);
IntersectionResult inter_result = trace_ray(brdf_sampled_direction);
if (inter_result.primitive == sampled_light)
{
    float distance_squared = inter_result.t * inter_result.t;
    float light_area = triangle_area(inter_result.primitive);

    float light_pdf = distance_squared / (light_area * cos_angle);

    float mis_weight = power_heuristic(sampled_pdf, light_pdf);
    float cosine_term = dot(shading_normal, brdf_sampled_direction);
    brdf_with_mis_contribution= brdf * cosine_term * inter_result.emission * mis_weight / direction_pdf;
}

return (light_with_mis_contribution  + brdf_with_mis_contribution) / (1.0f / nbEmissiveTriangles);
</code></pre>
<p>Here is the reference with 3k emissive triangles (which I assume is correct):</p>
<p><a href="https://i.sstatic.net/LegYGpdr.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/LegYGpdr.png" alt="Correct result when multiplying by the number of triangles in the return statement and not taking into account the number of emissive triangles in the PDF used in the MIS weight" /></a></p>
<p>Now if I try doing things the way I understand them, I would need to further divide the <code>light_sample_pdf</code> and <code>light_pdf</code> by the number of emissive triangles in the scene and I can drop the division by the inverse <code>/ (1.0f / nbEmissiveTriangles)</code> in the reutrn statement. This is because there's only a 1 in <code>nbEmissiveTriangles</code> to choose the specific triangle I uniformly chose.</p>
<p>The code then becomes:</p>
<pre><code>light_sample_pdf = 1.0f / sampled_light.area()
light_sample_pdf *= distance_to_light * distance_to_light;
light_sample_pdf /= dot_light_source;
light_sample_pdf /= nbEmissiveTriangles;

...
...

float light_area = triangle_area(inter_result.primitive);    
float light_pdf = distance_squared / (light_area * cos_angle) / nbEmissiveTriangles;


...
...

return light_with_mis_contribution  + brdf_with_mis_contribution;
</code></pre>
<p>However, doing things this way gives me the dark results I mentioned earlier:</p>
<p><a href="https://i.sstatic.net/fzP10OI6.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/fzP10OI6.png" alt="Dark results with the number of emissive triangles taken into account" /></a></p>
<p>The TL;DR is: Why am I getting dark results when I try to take the number of emissive triangles into account in the light PDF used in the MIS weight?</p>
<p><strong>EDIT:</strong> A <a href="https://github.com/TomClabault/HIPRT-Path-Tracer/blob/main/src/Device/includes/Lights.h#L148" rel="nofollow noreferrer">link</a> to the full MIS implementation on my repo.</p>

CC BY-SA 4.0
2024-06-19T07:59:24.520Z
Can you provide the reference to your code (if it is a open source repo)? Also,  what does `sampled_light.area()` return? For example, if `sampled_light` is a cube, does `area()` just return the surface area sum of all 12 triangles?
Enigmatisms
CC BY-SA 4.0
2024-06-19T08:28:26.887Z
@Enigmatisms I'm only ever sampling triangles in this case so sampled_light.area() is the area of the triangle. The code I posted is pseudo-code to avoid posting the entire method which I thought would bloat the post.

Here's the a link to the code base (link to the MIS implementation): https://github.com/TomClabault/HIPRT-Path-Tracer/blob/main/src/Device/includes/Lights.h#L148
Tom Clabault
<raytracing><pathtracing><global-illumination>
Multiple Importance Sampling BSDF + Light - weights confusion with the number of emissive triangles
Tom Clabault
# Multiple Importance Sampling BSDF + Light - weights confusion with the number of emissive triangles

(Asked by: username_0 on 2024-06-19)

I'm trying to implement multiple importance sampling for direct lighting estimation but I'm having a hard time understanding why adding the number of triangles to the PDF of my light sample yields very dark results:

Below is the code I found mostly by trials and errors which I assume gives correct results:

```
// Sample light with MIS
Light sampled_light = sample_random_point_on_lights(...);

light_sample_pdf = 1.0f / sampled_light.area()
light_sample_pdf *= distance_to_light * distance_to_light;
light_sample_pdf /= dot_light_source;

float brdf_pdf;
ColorRGB brdf = brdf(..., &brdf_pdf);

float mis_weight = power_heuristic(light_sample_pdf, brdf_pdf);

ColorRGB Li = sampled_light .emission;
float cosine_term = dot(shading_normal, shadow_ray.direction);

ColorRGB light_with_mis_contribution = Li * cosine_term * brdf * mis_weight / light_sample_pdf;

// Sample BRDF with MIS
float sampled_pdf;
Vector brdf_sampled_direction;
ColorRGB brdf = brdf_sample(..., &sampled_pdf, &brdf_sampled_direction);

ColorRGB brdf_with_mis_contribution(0.0f);
IntersectionResult inter_result = trace_ray(brdf_sampled_direction);
if (inter_result.primitive == sampled_light)
{
    float distance_squared = inter_result.t * inter_result.t;
    float light_area = triangle_area(inter_result.primitive);

    float light_pdf = distance_squared / (light_area * cos_angle);

    float mis_weight = power_heuristic(sampled_pdf, light_pdf);
    float cosine_term = dot(shading_normal, brdf_sampled_direction);
    brdf_with_mis_contribution= brdf * cosine_term * inter_result.emission * mis_weight / direction_pdf;
}

return (light_with_mis_contribution  + brdf_with_mis_contribution) / (1.0f / nbEmissiveTriangles);

```

Here is the reference with 3k emissive triangles (which I assume is correct):

[![Correct result when multiplying by the number of triangles in the return statement and not taking into account the number of emissive triangles in the PDF used in the MIS weight](https://i.sstatic.net/LegYGpdr.png)](https://i.sstatic.net/LegYGpdr.png)

Now if I try doing things the way I understand them, I would need to further divide the `light_sample_pdf` and `light_pdf` by the number of emissive triangles in the scene and I can drop the division by the inverse `/ (1.0f / nbEmissiveTriangles)` in the reutrn statement. This is because there's only a 1 in `nbEmissiveTriangles` to choose the specific triangle I uniformly chose.

The code then becomes:

```
light_sample_pdf = 1.0f / sampled_light.area()
light_sample_pdf *= distance_to_light * distance_to_light;
light_sample_pdf /= dot_light_source;
light_sample_pdf /= nbEmissiveTriangles;

...
...

float light_area = triangle_area(inter_result.primitive);    
float light_pdf = distance_squared / (light_area * cos_angle) / nbEmissiveTriangles;

...
...

return light_with_mis_contribution  + brdf_with_mis_contribution;

```

However, doing things this way gives me the dark results I mentioned earlier:

[![Dark results with the number of emissive triangles taken into account](https://i.sstatic.net/fzP10OI6.png)](https://i.sstatic.net/fzP10OI6.png)

The TL;DR is: Why am I getting dark results when I try to take the number of emissive triangles into account in the light PDF used in the MIS weight?

**EDIT:** A [link](https://github.com/username_0/HIPRT-Path-Tracer/blob/main/src/Device/includes/Lights.h#L148) to the full MIS implementation on my repo.

**Tags:** raytracing, pathtracing, global-illumination

> **Comments:**
> **username_1**: Can you provide the reference to your code (if it is a open source repo)? Also,  what does `sampled_light.area()` return? For example, if `sampled_light` is a cube, does `area()` just return the surface area sum of all 12 triangles?  
> **username_0**: @username_1 I'm only ever sampling triangles in this case so sampled_light.area() is the area of the triangle. The code I posted is pseudo-code to avoid posting the entire method which I thought would bloat the post.  Here's the a link to the code base (link to the MIS implementation): https://github.com/username_0/HIPRT-Path-Tracer/blob/main/src/Device/includes/Lights.h#L148  

---

## 1 answers

### Answer by username_1 on 2024-06-25. Score: 1

I am not sure whether this is the problem, but I do find something that does not make sense and can be improved. If I understand it correctly, what you are doing is:

* First sample an emissive triangle in a two-step way: (1) uniformly choose an emissive triangle from the buffer (therefore, the number of triangles **should** be accounted for as PDF) (2) uniformly sample a point on that triangle. Convert the emitter PDF to solid angle measure.
* Evaluate the PDF of BSDF sampling (in the same NEE direction) -> get PDF in solid angle measure.
* MIS, for emitter sampling.
* (Here starts what confuses me) BSDF sampling, trace a new ray (therefore, not an one-sample-model MIS). **If it hits the exact same triangle sampled in the emitter sample step**: evaluate emitter PDF and then perform MIS.

The part "If it hits the exact same triangle sampled in the emitter sample step" doesn't make sense:

* We know that the sampling event of the emissive triangle and BSDF sampling in the second step is independent: obviously, BSDF sampling itself in the second step has no dependency over which emissive triangle you sample. Therefore, let $\omega$ be the direction sampled via BSDF sampling, the PDF of BSDF samples a direction $\omega$ **and the ray in the same direction hits the sampled emissive triangle** should be:

$$
p\_{BSDF}(\omega) \times p\_{\text{hit}}(i)
$$

This is to say that ray with direction $\omega$ hitting the same emissive triangle is actually conditioned on the your sampling result, and I guess `brdf_with_mis_contribution` should be small, since it is only divided by `direction_pdf` and previously we will multiply both `brdf_with_mis_contribution` and `light_with_mis_contribution` by number of light primitives yet when you remove this multiplier, `brdf_with_mis_contribution` is far less than `light_with_mis_contribution`. This also changes your MIS weight in emitter sampling step.

I can't be exactly sure what $p\_{\text{hit}}(i)$ should be, and I think it would be the solid angle subtended by the sampled emissive triangle at the closest hit point, divided by $4\pi$.

I am not sure whether this is the case, but anyway here is my personal suggestion: you can refer to [mitsuba2/path.cpp](https://github.com/mitsuba-renderer/mitsuba2/blob/master/src/integrators/path.cpp) and check how they implement emitter MIS. Basically, BSDF sampling step is a bit wasteful since you only trace this ray to test whether it hits the emitter and if it doesn't, the ray is discarded. Mitsuba2 does a clever move: this BSDF sample is reused until the next ray intersection: if an emitter is hit, I compute the BSDF MIS weight for emission, while if a non-emissive triangle is hit, we can just continue the ray tracing and consider this BSDF sample as recursive ray update. This way, you can correctly account for BSDF sampling and don't need to further consider the case where an emitter is hit directly during path tracing.

> **Comments:**
> **username_0**: What is the "one-sample-model MIS"?  
> **username_1**: Check [Veach Chapter 9 MIS: 9.2.4  the one-sample model](https://graphics.stanford.edu/courses/cs348b-03/papers/veach-chapter9.pdf).  
> **username_0**: > Mitsuba2 does a clever move: this BSDF sample is reused until the next ray intersection:  A few months after, I realize that this is a smart optimization. Are there maybe other optimizations like this one (in Mitsuba or not) in the wild? i.e. not so common but good optimizations like this?  
> **username_1**: For this trick, AFAIK, Falcor has it (check [this](https://github.com/NVIDIAGameWorks/Falcor/blob/master/Source/RenderPasses/PathTracer/PathTracer.slang) out, line 891-921), yet Tungsten and pbrt-v3 do not implement this trick. For 'simple but smart' optimizations, the only one I can recall is the RR in the ratio tracking estimator, truly simple but smart: [Falcor GridVolumeSampler.slang, line 245-279](https://github.com/NVIDIAGameWorks/Falcor/blob/master/Source/Falcor/Rendering/Volumes/GridVolumeSampler.slang).  

---

