Skip to content

Commit 673e70c

Browse files
authored
Fix specular cutoff on lights with radius overlapping with mesh (#19157)
# Objective - Fixes #13318 ## Solution - Clamp a dot product to be positive to avoid choosing a `centerToRay` which is not on the ray but behind it. ## Testing - Repro in #13318 Main: <img width="963" alt="{DA2A2B99-27C7-4A76-83B6-CCB70FB57CAD}" src="https://github.com/user-attachments/assets/afae8001-48ee-4762-9522-e247bbe3577a" /> This PR: <img width="963" alt="{2C4BC3E7-C6A6-4736-A916-0366FBB618DA}" src="https://github.com/user-attachments/assets/5bea4162-0b58-4df0-bf22-09fcb27dc167" /> Eevee reference: ![329697008-ff28a5f3-27f3-4e98-9cee-d836a6c76aee](https://github.com/user-attachments/assets/a1b566ab-16ee-40d3-a0b6-ad179ca0fe3a)
1 parent eb0f4f7 commit 673e70c

File tree

1 file changed

+17
-1
lines changed

1 file changed

+17
-1
lines changed

crates/bevy_pbr/src/render/pbr_lighting.wgsl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,23 @@ fn compute_specular_layer_values_for_point_light(
278278

279279
// Representative Point Area Lights.
280280
// 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;
282298
let closestPoint = light_to_frag + centerToRay * saturate(
283299
light_position_radius * inverseSqrt(dot(centerToRay, centerToRay)));
284300
let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint));

0 commit comments

Comments
 (0)