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:
@@ -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 {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user