I'm struggling with light leaking through meshes (especially thin ones) due to interpolation between probes located on the opposite sides of those.

First approach I took was mapping all static geometry to 1 lightmap, then generating light probes for each texel center that can be mapped to some triangle. Compute probe world position using barycentric coords of a texel on a corresponding static geometry triangle and then, in fragment shader, bilinearly interpolate between probes using fragment's UV coordinate. Unfortunately, I had to throw all of this away, because automatic UV mapping (all of the tools available in Blender, for example) are absolutely not suitable for this task and the manual UV crafting is just too tedious to be viable.

Then I saw a Guerrilla Games presentation regarding GI tech in the Killzone which leverages shadow maps (kind of) to store occlusion data for each probe. I thought maybe I could pull off something similar. What I did was ray trace occlusions for each probe and store occlusion distances for respective ray directions in a huge texture atlas, then, in a fragment shader, determine which probes are occluded for the current pixel by comparing distance from probe to pixel with occlusion distance for the nearest ray and set interpolation weights for occluded probes to 0. But, there are 2 serious problems to this approach, which I found only after implementing the algorithm.

1) Let's consider the case, when distance from the current pixel to the probe is compared to ray 3 (see attached illustration). For pixels located in the range denoted 'l' distance to probe will be greater than occlusion distance for ray 3 and the probe will be considered occluded, but it's not!

First problem

2) Let's imagine the first problem is solved. Now the problem of a thin mesh arises. For such mesh pixels on the opposite sides of it will have pretty much the same world location and distance tests will result in full interpolation of probes from both sides for both pixels. I guess a solution for this will require pixel's normal, but maybe it will come after the first issue is resolved.

Second problem

I would greatly appreciate any help in solving there problems. Or maybe someone could point towards more viable algorithm instead?


1 Answer 1


I've also spent a good amount of time thinking about a solution for this problem. It seems like one those fundamental mismatches of taking a sparser, lower frequency structure (probe mesh, even if augmented with depth map) and lighting surfaces per pixel which themselves are occluders.

If you look at the SIGGRAPH'17 Precomputed lighting in CoD:IW (http://advances.realtimerendering.com/s2017/index.html) you'll see they partitioned the precomputed lighting into light maps for large surfaces (which have lots of occlusion) and using a probe mesh for small static objects as well as characters. They take a similar approach of storing an occlusion depth map per tetrahedra face, however because they're not lighting the occluders with the probe mesh, the discontinuities introduced are likely not noticeable.

Trying to light the occluders with the same map is extremely tricky because the probe mesh faces may not align well with the occluders. I.e. literally "corner" case where 3 planes meet doesn't encode well into a depth map. I have feeling they still get light leaks, but their density is probably fairly high and their evaluation points are distant enough from the pathologically bad cases.

Sometimes it might be worth asking level artists to not make single sided meshes or thin walls XD, at least for the time being.

Best of luck, if you find a robust solution to this, you should submit a paper to a conference and be graphics industry famous :D

  • $\begingroup$ Thanks for your thoughts, Calvin. I've studied the paper you provided and it seems that they use the same tetrahedron/occlusion approach as Guerilla Games does. And Guerilla Games mention using same probe system for dynamic and static geometry which implies that lit surfaces are occluders themselves. I guess they don't have thin geometry or something, because in my case, the whole thing looks awful! And so, I switched to simply multiplying interpolation weights by clamped cosines between fragment-to-probe and fragment normal vectors, which is what Quantum Break's engine does, apparently. $\endgroup$ Commented May 29, 2018 at 9:19
  • $\begingroup$ Some leakage is present, of course, but still, it looks much better than all other things I've tried. I'll have to wait untill multiple light bounces are done to see whether leakage is masked by them. If I'll find final result to be pleasing, I'll post an answer. $\endgroup$ Commented May 29, 2018 at 9:28
  • $\begingroup$ Late to the party but I was just experimenting with this myself, and also used the geometric factor from Quantum Break, i.e.: saturate(dot(normal, posToProbe)); However, this causes some blobby look especially on planar geometry. I managed to get rid off of that problem by multiplying the geometric factor by distance from the pixel to the probe, i.e. length(posToProbe) $\endgroup$
    – maxest
    Commented Jul 26, 2023 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.