-
Notifications
You must be signed in to change notification settings - Fork 964
Description
I have been working through this series for a number of years as a hobbyist. Book 3 has equipped me with the knowledge to upgrade my Whitted-style ray tracer to a path ray tracer. While I am still working through Book 3 I wanted to say a big Thank you!
Onto the topic, I would like to discuss the "Surface Acne" that is produced after introducing the concept of PDFs. Aka, those pesky and ugly black pixels.
After introducing the concept of PDFs in Book 3, my implementation also exhibited NaN pixels as early as chapter 6. I tried the book’s reference implementation and read the accompanying explanation in chapter 12.6 to resolve the issue, but the explanation does not make sense and the proposed solution does not work as intended.
Within the write_color function we have:
raytracing.github.io/src/common/color.h
Lines 24 to 27 in ec8b43e
| // Replace NaN components with zero. See explanation in Ray Tracing: The Rest of Your Life. | |
| if (r != r) r = 0.0; | |
| if (g != g) g = 0.0; | |
| if (b != b) b = 0.0; |
However, I believe that it is too late in the process to fix these pixels at the output stage. What the above says is:
If Pixel is Bad then
Set Pixel = Black
Thus, these bad pixels are still being outputted as black.
What I think the intention of the code and explanation is actually:
Loop over camera height and width pixels:
pixel_color = empty;
Loop over samples:
color_contribution = ray_color(ray, depth);
If color_contribution is Bad then
Set color_contribution = Black;
pixel_color += color_contribution;
This way, one bad sample does not ruin all the samples and the final pixel.
Thus, I would remove the lines
raytracing.github.io/src/common/color.h
Lines 24 to 27 in ec8b43e
| // Replace NaN components with zero. See explanation in Ray Tracing: The Rest of Your Life. | |
| if (r != r) r = 0.0; | |
| if (g != g) g = 0.0; | |
| if (b != b) b = 0.0; |
from the write color_write function, and check for bad/NaN samples in the main rendering loop as outlined above. This would also limit the scope of the fix to Book 3 (where it is introduced).
I could end the issue here as I believe that Chapter 12.6 serves as a learning exercise – in both graphics and floating-point math. However, I don’t want to accept a known bug in my implementation especially when I believe the source of the bug is an easy fix.
We seem to get NaN samples when we divide calculating color contribution sample by zero. Specifically here
raytracing.github.io/src/TheRestOfYourLife/pdf.h
Lines 33 to 36 in ec8b43e
| double value(const vec3& direction) const override { | |
| auto cosine_theta = dot(unit_vector(direction), uvw.w()); | |
| return fmax(0, cosine_theta/pi); | |
| } |
, where the PDF can be zero. So let’s not allow it to be zero. 😃 Let’s make it as close to zero as possible. We could simply do the following:
return fmax(DBL_EPSILON, cosine / PI);
Note, that this could also be applied more generally higher up in the ray_color member function too.
I have attached a few rendering plates, although I am unsure if they actually help since they are only showing the known bug and a clean render. But perhaps the root cause of the bug can be left as a exercise for the reader to investigate. 😃
