Fix: Cycles spot light spread sampling not considering non-uniform scaling

For spherical spot light, when the shading point is close to the light
source, we switch to sampling the light spread instead of the visible
cone from the shading point. This has the benefit of less noise when the
spread is small.
However, the light spread sampling was not considering non-uniform
object scaling, where the actual spread might be different.
This patch switches sampling method only when the smallest enclosing
spread cone is smaller than the visible cone from the shading point.

An alternative method would be to compute the actual solid angle of the
scaled cone, and sample from the scaled cone. However, that involves
ray transformation and modifying the sampling pdf and angle. Since
non-uniform scaling is rather a niche case, it's probably not worth the
computation effort.

Pull Request: https://projects.blender.org/blender/blender/pulls/119661
This commit is contained in:
Weizhen Huang
2024-03-18 18:41:40 +01:00
committed by Gitea
parent a6fba7b59d
commit a2bb547b9a
3 changed files with 20 additions and 8 deletions

View File

@@ -60,7 +60,7 @@ ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
ls->t = FLT_MAX;
if (d_sq > r_sq) {
/* Outside sphere. */
const float one_minus_cos_half_spot_spread = 1.0f - klight->spot.cos_half_spot_angle;
const float one_minus_cos_half_spot_spread = 1.0f - klight->spot.cos_half_larger_spread;
const float one_minus_cos_half_angle = sin_sqr_to_one_minus_cos(r_sq / d_sq);
if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) {
@@ -147,7 +147,7 @@ ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
return true;
}
ccl_device_forceinline float spot_light_pdf(const float cos_half_spread,
ccl_device_forceinline float spot_light_pdf(const ccl_global KernelSpotLight *spot,
const float d_sq,
const float r_sq,
const float3 N,
@@ -155,7 +155,8 @@ ccl_device_forceinline float spot_light_pdf(const float cos_half_spread,
const uint32_t path_flag)
{
if (d_sq > r_sq) {
return M_1_2PI_F / min(sin_sqr_to_one_minus_cos(r_sq / d_sq), 1.0f - cos_half_spread);
return M_1_2PI_F /
min(sin_sqr_to_one_minus_cos(r_sq / d_sq), 1.0f - spot->cos_half_larger_spread);
}
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
@@ -183,7 +184,7 @@ ccl_device_forceinline void spot_light_mnee_sample_update(const ccl_global Kerne
/* NOTE : preserve pdf in area measure. */
const float jacobian_solid_angle_to_area = 0.5f * fabsf(d_sq - r_sq - t_sq) /
(radius * ls->t * t_sq);
ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ls->D, path_flag) *
ls->pdf = spot_light_pdf(&klight->spot, d_sq, r_sq, N, ls->D, path_flag) *
jacobian_solid_angle_to_area;
ls->Ng = normalize(ls->P - klight->co);
@@ -234,7 +235,7 @@ ccl_device_inline bool spot_light_sample_from_intersection(
ls->eval_fac = klight->spot.eval_fac;
if (klight->spot.is_sphere) {
ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ray_D, path_flag);
ls->pdf = spot_light_pdf(&klight->spot, d_sq, r_sq, N, ray_D, path_flag);
ls->Ng = normalize(ls->P - klight->co);
}
else {

View File

@@ -1374,6 +1374,8 @@ typedef struct KernelSpotLight {
float half_cot_half_spot_angle;
float spot_smooth;
int is_sphere;
/* For non-uniform object scaling, the actual spread might be different. */
float cos_half_larger_spread;
} KernelSpotLight;
/* PointLight is SpotLight with only radius and invarea being used. */

View File

@@ -1346,13 +1346,22 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
klights[light_index].area.normalize_spread = normalize_spread;
}
if (light->light_type == LIGHT_SPOT) {
float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = 1.0f / ((1.0f - cos_half_spot_angle) * light->spot_smooth);
const float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
const float spot_smooth = 1.0f / ((1.0f - cos_half_spot_angle) * light->spot_smooth);
const float tan_half_spot_angle = tanf(light->spot_angle * 0.5f);
const float len_w_sq = len_squared(light->get_dir());
const float len_u_sq = len_squared(light->get_axisu());
const float len_v_sq = len_squared(light->get_axisv());
const float tan_sq = sqr(tan_half_spot_angle);
klights[light_index].spot.dir = safe_normalize(light->get_dir());
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
klights[light_index].spot.half_cot_half_spot_angle = 0.5f / tanf(light->spot_angle * 0.5f);
klights[light_index].spot.half_cot_half_spot_angle = 0.5f / tan_half_spot_angle;
klights[light_index].spot.spot_smooth = spot_smooth;
/* Choose the angle which spans a larger cone. */
klights[light_index].spot.cos_half_larger_spread = inversesqrtf(
1.0f + tan_sq * fmaxf(len_u_sq, len_v_sq) / len_w_sq);
}
klights[light_index].shader_id = shader_id;