After completing a path tracing render of the Cornell Box recently, I thought about an interesting experiment. Since it’s a fully diffuse scene, it could be rendered using finite element radiosity, and the two results could be compared, helping to validate and sanity check both approaches.
In path tracing, an entire wall of the Cornell Box can be one single square polygon. But in radiosity, the illumination is calculated per polygon (or ‘patch’).
I added code to subdivide meshes, either by iteratively subdividing some number of times, or by subdividing until every polygon is under some maximum area.
Ultimately, I chose to start with around 1000 patches.
Then, code was added to convert meshes to the Radiosity data structures: entities and patches. The result using around 1000 patches (3 subdivisions) and 32-pixel hemicubes is already quite encouraging!
There are a few classic artifacts of radiosity visible in the image above. First, the edges and corners of the room are brighter than they are in path tracing, due to the fact that we’re evaluating the lighting in the center of a nearby patch rather than inside the corner. Second, the shadows of the boxes don’t line up with the corners of the box perfectly. This is especially visible on the smaller box. Third, if you look carefully, you can pick out the Gouraud style shading artifacts inherent in interpolating the lighting from vertices across the polygons.
Increasing the patch count to around 11,000 helps to greatly reduce these artifacts! Below, you can see path tracing on the left and radiosity on the right.
This is an encouraging result, but I noticed that the front face of the tall cube is uniformly brighter in radiosity than it is in path tracing. This turned out to be a tricky problem to debug, since the lighting code in each approach is completely separate. After a few unsuccessful attempts to debug the issue in this scene, I moved on to a simpler scene. You might recall the “emmision” test scene from the Utah Global Illumination Test Scenes from a past post in this blog. Here’s the result of rendering that scene as-is with radiosity.
The super flat look is a reminder we’re only calculating the lighting per patch in radiosity. So the next step is to find a subdivision amount that’s reasonable.
After choosing subdivision to max area of 200, it’s time to render a recent path tracing render of the same scene and compare. The good news is that the difference is visible here as well! The radiosity version seems to have expanded highlights. The light falls on the ground in a slightly bigger pattern.
To comapare pixel values, it’s useful to turn off interpolation in the radiosity version. I need to compare exact patch values to the same exact (patch center) location in the path tracing version.
At this point it’s worth noting that it was very tempting to chalk this difference up to some quirk of integration using these different methods with non-infinite segmentation. I’m happy to say though that the problem was found! Path tracing ended up being the culprit, which surprised me. I spent quite an amount of time stepping through the radiosity code before deciding the check the path tracing. The issue ended up being a 10 year old “test” code snippet I was using to test a fresnel effect. I never removed it back then, and it ended up biting me now. I’m glad I didn’t chalk this up to some unknowable quirk. Removing that term imnmediately brought the two results into allignment. Here’s the fixed comparison for the emission scene:
And here’s the same fix applied to the Cornell Box. A much better match!
This is an interesting read! I was also suffering from a rendering artifact recently, and spent a great deal of time (5 days) debugging it which is really painful…
Congratulations on finding the bug!
Thank you for reading! Yes, some of these bugs can be very subtle and difficult to track down, but it’s so satisfying once the problem is fixed!