mmp / pbrt-v3

Source code for pbrt, the renderer described in the third edition of "Physically Based Rendering: From Theory To Implementation", by Matt Pharr, Wenzel Jakob, and Greg Humphreys.
http://pbrt.org
BSD 2-Clause "Simplified" License
4.86k stars 1.18k forks source link

Samples "leak out" from the medium due to imperfect ray-intersection logic #328

Open Enigmatisms opened 1 year ago

Enigmatisms commented 1 year ago

I have been working on pbrt-v3 for volumetric rendering, and I found this problem:

Here is how samples "leak out" from the medium:

Here is the experiment illustrating my findings, note that the medium box is bounded in [0, 1] (for all x, y, z axis, so you should not see coordinates greater than 1 or less than 0), this is the terminal output:

ray starting at (0.000000, 0.621527, 0.091540) has no intersection.
Newly generated medium interaction is at (-0.003284, 0.705012, 0.093444)
ray starting at (-0.003284, 0.705012, 0.093444) has no intersection.
Newly generated medium interaction is at (-0.006952, 0.697732, 0.102618)
ray starting at (-0.006952, 0.697732, 0.102618) has no intersection.
Newly generated medium interaction is at (-0.065556, 0.628726, 0.039051)
ray starting at (-0.065556, 0.628726, 0.039051) has no intersection.
Newly generated medium interaction is at (-0.069751, 0.630400, 0.013837)
ray starting at (-0.069751, 0.630400, 0.013837) has no intersection.
Newly generated medium interaction is at (-0.257248, 0.561523, 0.116825)
ray starting at (-0.257248, 0.561523, 0.116825) has no intersection.
Newly generated medium interaction is at (-0.280786, 0.589093, 0.151559)
ray starting at (-0.280786, 0.589093, 0.151559) has no intersection.
...

We can see that, the first MediumInteraction is generated at (0.000000, 0.621527, 0.091540), right on the boundary (maybe inside due to the limitation of printing floating digits), the intersection is invalidated but the distance sampling continues, and then it goes on and on outside of the medium, wandering further away and producing bias.

Here is the code I modified to print the output, in volpath.cpp, starting from line 79:

if (ray.medium) {
    beta *= ray.medium->Sample(ray, sampler, arena, &mi);
    if (!foundIntersection) {
        printf("ray starting at (%f, %f, %f) has no intersection.\n", ray.o[0], ray.o[1], ray.o[2]);
        printf("Newly generated medium interaction is at (%f, %f, %f)\n", mi.p[0], mi.p[1], mi.p[2]);
    }
}

So I suppose, the whole thing is caused by the invalidating mechanism in ray-intersection: medium does not know the ray should be classified as being outside of the medium, so the sampling continues. The solution here is however, not easy, I suppose. If the scene is more complex and has other objects, pbrt-v3 might use a further-away intersection point. This would make the renderer confused. I think the solution is Intersect function returns a status flag, indicating how the intersection failed. If we found an intersection invalidated due to being too close to the triangle, we should test the normal and the ray direction to check if we are inside of the object, if so then we notify the medium sampler.

To reproduce, the scene file can be found in github-gist/volume.pbrt, using just the lastest version of this repo and modify volpath.cpp with the code, then you can see this effect, very easy to reproduce. Note that this phenomenon is not rare during rendering.