Cycles: Fallback to area PDF when spherical rectangles are too small

This fixes #69535 and #98930.

We use a equi-solid-angle sampling algorithm for rectangular area lights,
but it is not particularly robust for small area lights (either small
in general and/or small because it's being viewed from grazing angles).

The actual sampling part is fine since it just gets clamped into the
valid area anyways, and the difference isn't notable for small lights.

However, we also need to compute the solid angle to get the sampling PDF,
and that computation is quite sensitive to numerical issues for small
values.

Therefore, this commit adds a fallback path for small values, which instead
uses the classic equi-area sampling PDF term times the area-to-solid-angle
Jacobian term. This approximation assumes that all points on the light have
the same distance and angle to the sampling point, which is of course not
strictly the case, but it's close enough for small area lights and better
than failing altogether.

Pull Request: https://projects.blender.org/blender/blender/pulls/122323
This commit is contained in:
Lukas Stockner 2024-05-27 04:55:08 +02:00
parent a5dc233aa2
commit fbc770d58b
2 changed files with 17 additions and 8 deletions

View file

@ -81,11 +81,20 @@ ccl_device_inline float area_light_rect_sample(float3 P,
}
/* return pdf */
if (S != 0.0f) {
return 1.0f / S;
if (S < 1e-5f || reduce_min(sqr(nz)) > 0.99999f) {
/* The solid angle is too small to be computed accurately in single precision.
* As a fallback, approximate it using the planar sampling PDF,
* for such tiny lights the difference is irrelevant.
*
* A threshold of 1e-5 was found to be the smallest option that avoids structured
* artifacts at all tested parameter combinations. The additional check of nz is
* needed for the case where the light is viewed from grazing angles, see e.g. #98930.
*/
const float t = len(dir);
return -t * t * t / (z0 * len_u * len_v);
}
else {
return 0.0f;
return 1.0f / S;
}
}

View file

@ -387,6 +387,11 @@ ccl_device_inline float len_squared(const float4 a)
return dot(a, a);
}
ccl_device_inline float4 sqr(const float4 a)
{
return a * a;
}
#if !defined(__KERNEL_METAL__)
ccl_device_inline float distance(const float4 a, const float4 b)
{
@ -412,11 +417,6 @@ ccl_device_inline float4 sqrt(const float4 a)
# endif
}
ccl_device_inline float4 sqr(const float4 a)
{
return a * a;
}
ccl_device_inline float4 cross(const float4 a, const float4 b)
{
# ifdef __KERNEL_SSE__