@@ -278,7 +278,23 @@ fn compute_specular_layer_values_for_point_light(
278
278
279
279
// Representative Point Area Lights.
280
280
// see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16
281
- let centerToRay = dot (light_to_frag , R ) * R - light_to_frag ;
281
+ var LtFdotR = dot (light_to_frag , R );
282
+
283
+ // HACK: the following line is an amendment to fix a discontinuity when a surface
284
+ // intersects the light sphere. See https://github.com/bevyengine/bevy/issues/13318
285
+ //
286
+ // This sentence in the reference is crux of the problem: "We approximate finding the point with the
287
+ // smallest angle to the reflection ray by finding the point with the smallest distance to the ray."
288
+ // This approximation turns out to be completely wrong for points inside or near the sphere.
289
+ // Clamping this dot product to be positive ensures `centerToRay` lies on ray and not behind it.
290
+ // Any non-zero epsilon works here, it just has to be positive to avoid a singularity at zero.
291
+ // However, this is still far from physically accurate. Deriving an exact solution would help,
292
+ // but really we should adopt a superior solution to area lighting, such as:
293
+ // Physically Based Area Lights by Michal Drobot, or
294
+ // Polygonal-Light Shading with Linearly Transformed Cosines by Eric Heitz et al.
295
+ LtFdotR = max (0.0001 , LtFdotR );
296
+
297
+ let centerToRay = LtFdotR * R - light_to_frag ;
282
298
let closestPoint = light_to_frag + centerToRay * saturate (
283
299
light_position_radius * inverseSqrt (dot (centerToRay, centerToRay)));
284
300
let LspecLengthInverse = inverseSqrt (dot (closestPoint, closestPoint));
0 commit comments