RayTracing / raytracing.github.io

Main Web Site (Online Books)
https://raytracing.github.io/
Creative Commons Zero v1.0 Universal
8.9k stars 882 forks source link

Book 1.10.6: Dark edge around hittables with Metal material #1285

Open 0xCache opened 1 year ago

0xCache commented 1 year ago

Hi, I'm following the first book's guide and noticed some parts around the fuzzy metal hittable sphere are darker than the center. I suspected it's because when the reflected vector is closer to the surface, where dot(hit.normal, reflected) is closer to 0, a random unit vector multiplied by a big fuzz will make the final scattered ray point inside the sphere. I added a dot check, multiplied the fuzz vector by the dot product, and solved the problem. Before:

image

After:

image
hollasch commented 7 months ago

Tempted to close this as "by design". From the book, with my emphasis added:

The bigger the fuzz sphere, the fuzzier the reflections will be. This suggests adding a fuzziness parameter that is just the radius of the sphere (so zero is no perturbation). The catch is that for big spheres or grazing rays, we may scatter below the surface. We can just have the surface absorb those.

I agree that this is not an ideal solution, but I inclined to think that formally covering a possible fix for this isn't worth it.

hollasch commented 7 months ago

Hmmm, perhaps we should just put more guardrails around the fuzz factor. Current code:

metal(const color& albedo, double fuzz) : albedo(albedo), fuzz(fuzz < 1 ? fuzz : 1) {}

Suggested update:

metal(const color& albedo, double fuzz) : albedo(albedo), fuzz(fmin(fabs(fuzz), 0.99)) {}
hollasch commented 7 months ago

Nope, the above won't work, since the "fuzz sphere" will more easily intersect with the surface for grazing rays. This is the purpose of the return (dot(scattered.direction(), rec.normal) > 0) statement at the end of metal::scatter().

0xCache commented 7 months ago

The problem is that although only returning when not pointing into the sphere works, some energy carried by the rays is lost when rays are absorbed, resulting in a darker colour. So I choose to make the absorbed rays point out. The code first uses a dot product, and returns the scattered ray if the product is greater than 0. If not, return the tangent.

double scale = dot(hit.normal, reflected);
Vec direction = dot(reflected + fuzz, hit.normal) > 0 ? reflected + fuzz : reflected + (scale- 1e-5) * fuzz;

A draft: image