chunky-dev / chunky

A path tracer to create realistic images of your Minecraft worlds.
https://chunky-dev.github.io/docs
GNU General Public License v3.0
647 stars 76 forks source link

Simplify emitter logic and make it more correct #1621

Closed JustinTimeCuber closed 11 months ago

JustinTimeCuber commented 1 year ago

The problem

As has been discussed previously (#1555, #1549), the apparent brightness of emitters when viewed directly does not scale with their emittance. While investigating this, it became clear that there was a deeper issue here. See this render done on master: image Emitter intensity is set to 0, so the blocks themselves shouldn't be lit up at all. But not only are they lit up, they are somehow slightly illuminating the scene. To simplify things, consider the case where all surfaces are diffuse. Looking at the relevant code: image indirectEmitterColor and next.emittance will always be zero, and emittance is forced to be zero after the first diffuse reflection (which as far as I can tell is just a workaround that makes things look less wrong). Thus, everything but the emitters themselves should be dark, and sure enough, even at 100x exposure: image A hint to where the light is coming from is that it is only noticeable near the back wall when it is a metallic/specular surface. Specular reflections aren't nearly as complicated, and don't have the same special case for handling reflected emitter light that diffuse surfaces do. From here, I tried making the back wall metallic, but extremely rough (0% smoothness). This should be virtually identical to what the diffuse surface from before looked like, but it's not: image You'll notice that this looks a lot like you'd expect a diffuse surface to look if emitters were enabled. That led me to this solution:

The solution

First, I tried making the floor and other walls metallic with zero smoothness (same as the back wall): image As expected, it looks like a completely normal render of some emitters in a diffuse room. I then began implementing this in code by changing the diffuse reflection logic to be more similar to the specular reflection logic, which basically boiled down to removing the next.emittance term while also removing the special case that handles emitter contributions differently after the first bounce. The main result is that now, "apparent emitter brightness" and "emitter light intensity" (terminology from #1555) are intrinsically linked rather than being related by a completely arbitrary factor of 13 - this is the "making it more correct" part. Additionally, these changes result in simpler code, most importantly by completely removing ray.emittance which is no longer needed. Not only does this make things a bit easier to read, it also improves performance slightly, because that's one less public Vector3 being allocated for each Ray.

Comparisons

After changes (328/328/328 seconds, 12% faster): image Before changes (368/369/368 seconds): image After changes (303/302/303 seconds, 11% faster): image Before changes (335/336/336 seconds): image