diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 848b5aa6251..b9614a46a7c 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1587,6 +1587,7 @@ class CYCLES_LIGHT_PT_light(CyclesButtonsPanel, Panel): col.separator() if light.type in {'POINT', 'SPOT'}: + col.prop(light, "use_soft_falloff") col.prop(light, "shadow_soft_size", text="Radius") elif light.type == 'SUN': col.prop(light, "angle") @@ -1601,6 +1602,7 @@ class CYCLES_LIGHT_PT_light(CyclesButtonsPanel, Panel): sub.prop(light, "size_y", text="Y") if not (light.type == 'AREA' and clamp.is_portal): + col.separator() sub = col.column() sub.prop(clamp, "max_bounces") diff --git a/intern/cycles/blender/light.cpp b/intern/cycles/blender/light.cpp index 23c780c7831..6eaa4529250 100644 --- a/intern/cycles/blender/light.cpp +++ b/intern/cycles/blender/light.cpp @@ -48,6 +48,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, BL::PointLight b_point_light(b_light); light->set_size(b_point_light.shadow_soft_size()); light->set_light_type(LIGHT_POINT); + light->set_is_sphere(!b_point_light.use_soft_falloff()); break; } case BL::Light::type_SPOT: { @@ -56,6 +57,7 @@ void BlenderSync::sync_light(BL::Object &b_parent, light->set_light_type(LIGHT_SPOT); light->set_spot_angle(b_spot_light.spot_size()); light->set_spot_smooth(b_spot_light.spot_blend()); + light->set_is_sphere(!b_spot_light.use_soft_falloff()); break; } /* Hemi were removed from 2.8 */ diff --git a/intern/cycles/kernel/light/area.h b/intern/cycles/kernel/light/area.h index 197aefb4216..807ada0de03 100644 --- a/intern/cycles/kernel/light/area.h +++ b/intern/cycles/kernel/light/area.h @@ -308,7 +308,7 @@ ccl_device_inline bool area_light_eval(const ccl_global KernelLight *klight, } if (in_volume_segment || (!sample_rectangle && klight->area.tan_half_spread > 0)) { - ls->pdf *= lamp_light_pdf(Ng, -ls->D, ls->t); + ls->pdf *= light_pdf_area_to_solid_angle(Ng, -ls->D, ls->t); } return ls->eval_fac > 0; @@ -368,7 +368,7 @@ ccl_device_forceinline void area_light_mnee_sample_update(const ccl_global Kerne ls->D = normalize_len(ls->P - P, &ls->t); area_light_eval(klight, P, &ls->P, ls, zero_float2(), false); /* Convert pdf to be in area measure. */ - ls->pdf /= lamp_light_pdf(ls->Ng, -ls->D, ls->t); + ls->pdf /= light_pdf_area_to_solid_angle(ls->Ng, -ls->D, ls->t); } } diff --git a/intern/cycles/kernel/light/background.h b/intern/cycles/kernel/light/background.h index 002fa6e4b74..50eb60b83e1 100644 --- a/intern/cycles/kernel/light/background.h +++ b/intern/cycles/kernel/light/background.h @@ -193,7 +193,7 @@ ccl_device_inline float background_portal_pdf( if (is_round) { float t; float3 D = normalize_len(lightpos - P, &t); - portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t); + portal_pdf += fabsf(klight->area.invarea) * light_pdf_area_to_solid_angle(dir, -D, t); } else { portal_pdf += area_light_rect_sample( @@ -256,7 +256,7 @@ ccl_device float3 background_portal_sample(KernelGlobals kg, lightpos += ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, rand); float t; D = normalize_len(lightpos - P, &t); - *pdf = fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t); + *pdf = fabsf(klight->area.invarea) * light_pdf_area_to_solid_angle(dir, -D, t); } else { *pdf = area_light_rect_sample(P, &lightpos, axis_u, len_u, axis_v, len_v, rand, true); diff --git a/intern/cycles/kernel/light/common.h b/intern/cycles/kernel/light/common.h index df903e1f601..ea724991817 100644 --- a/intern/cycles/kernel/light/common.h +++ b/intern/cycles/kernel/light/common.h @@ -49,7 +49,7 @@ ccl_device float3 disk_light_sample(float3 n, float2 rand) return ellipse_sample(ru, rv, rand); } -ccl_device float lamp_light_pdf(const float3 Ng, const float3 I, float t) +ccl_device float light_pdf_area_to_solid_angle(const float3 Ng, const float3 I, float t) { float cos_pi = dot(Ng, I); diff --git a/intern/cycles/kernel/light/point.h b/intern/cycles/kernel/light/point.h index 92dd02c96f4..595aece8089 100644 --- a/intern/cycles/kernel/light/point.h +++ b/intern/cycles/kernel/light/point.h @@ -15,48 +15,61 @@ ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight, const int shader_flags, ccl_private LightSample *ls) { + const float r_sq = sqr(klight->spot.radius); + float3 lightN = P - klight->co; const float d_sq = len_squared(lightN); const float d = sqrtf(d_sq); lightN /= d; - const float r_sq = sqr(klight->spot.radius); + ls->eval_fac = klight->spot.eval_fac; - float cos_theta; - if (d_sq > r_sq) { - const float one_minus_cos = sin_sqr_to_one_minus_cos(r_sq / d_sq); - ls->D = sample_uniform_cone(-lightN, one_minus_cos, rand, &cos_theta, &ls->pdf); - } - else { - const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION); - if (has_transmission) { - ls->D = sample_uniform_sphere(rand); - ls->pdf = M_1_2PI_F * 0.5f; + if (klight->spot.is_sphere) { + /* Spherical light geometry. */ + float cos_theta; + if (d_sq > r_sq) { + /* Outside sphere. */ + const float one_minus_cos = sin_sqr_to_one_minus_cos(r_sq / d_sq); + ls->D = sample_uniform_cone(-lightN, one_minus_cos, rand, &cos_theta, &ls->pdf); } else { - sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf); + /* Inside sphere. */ + const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION); + if (has_transmission) { + ls->D = sample_uniform_sphere(rand); + ls->pdf = M_1_2PI_F * 0.5f; + } + else { + sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf); + } + cos_theta = -dot(ls->D, lightN); } - cos_theta = -dot(ls->D, lightN); - } - /* Law of cosines. */ - ls->t = d * cos_theta - copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq); + /* Law of cosines. */ + ls->t = d * cos_theta - + copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq); - ls->P = P + ls->D * ls->t; - - ls->eval_fac = klight->spot.eval_fac; - if (r_sq == 0) { - /* Use intensity instead of radiance for point light. */ - ls->eval_fac /= sqr(ls->t); - /* `ls->Ng` is not well-defined for point light, so use the incoming direction instead. */ - ls->Ng = -ls->D; - } - else { - ls->Ng = normalize(ls->P - klight->co); /* Remap sampled point onto the sphere to prevent precision issues with small radius. */ + ls->P = P + ls->D * ls->t; + ls->Ng = normalize(ls->P - klight->co); ls->P = ls->Ng * klight->spot.radius + klight->co; } + else { + /* Point light with ad-hoc radius based on oriented disk. */ + ls->P = klight->co; + if (r_sq > 0.0f) { + ls->P += disk_light_sample(lightN, rand) * klight->spot.radius; + } + ls->D = normalize_len(ls->P - P, &ls->t); + ls->Ng = -ls->D; + + /* PDF. */ + const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f; + ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ls->D, ls->t); + } + + /* Texture coordinates. */ const Transform itfm = klight->itfm; const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng)); /* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */ @@ -66,7 +79,7 @@ ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight, return true; } -ccl_device_forceinline float point_light_pdf( +ccl_device_forceinline float sphere_light_pdf( const float d_sq, const float r_sq, const float3 N, const float3 D, const uint32_t path_flag) { if (d_sq > r_sq) { @@ -87,26 +100,26 @@ ccl_device_forceinline void point_light_mnee_sample_update(const ccl_global Kern const float radius = klight->spot.radius; - if (radius > 0) { + if (klight->spot.is_sphere) { const float d_sq = len_squared(P - klight->co); const float r_sq = sqr(radius); const float t_sq = sqr(ls->t); - ls->pdf = point_light_pdf(d_sq, r_sq, N, ls->D, path_flag); - /* NOTE : preserve pdf in area measure. */ - ls->pdf *= 0.5f * fabsf(d_sq - r_sq - t_sq) / (radius * ls->t * t_sq); + const float jacobian_solid_angle_to_area = 0.5f * fabsf(d_sq - r_sq - t_sq) / + (radius * ls->t * t_sq); + ls->pdf = sphere_light_pdf(d_sq, r_sq, N, ls->D, path_flag) * jacobian_solid_angle_to_area; ls->Ng = normalize(ls->P - klight->co); } else { - ls->eval_fac = klight->spot.eval_fac; + /* NOTE : preserve pdf in area measure. */ + ls->pdf = ls->eval_fac * 4.0f * M_PI_F; ls->Ng = -ls->D; - - /* PDF does not change. */ } + /* Texture coordinates. */ const Transform itfm = klight->itfm; const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng)); /* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */ @@ -123,8 +136,16 @@ ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *kligh return false; } - float3 P; - return ray_sphere_intersect(ray->P, ray->D, ray->tmin, ray->tmax, klight->co, radius, &P, t); + if (klight->spot.is_sphere) { + float3 P; + return ray_sphere_intersect(ray->P, ray->D, ray->tmin, ray->tmax, klight->co, radius, &P, t); + } + else { + float3 P; + const float3 diskN = normalize(ray->P - klight->co); + return ray_disk_intersect( + ray->P, ray->D, ray->tmin, ray->tmax, klight->co, diskN, radius, &P, t); + } } ccl_device_inline bool point_light_sample_from_intersection( @@ -136,25 +157,34 @@ ccl_device_inline bool point_light_sample_from_intersection( const uint32_t path_flag, ccl_private LightSample *ccl_restrict ls) { + const float r_sq = sqr(klight->spot.radius); + ls->eval_fac = klight->spot.eval_fac; - const float radius = klight->spot.radius; - - ls->Ng = radius > 0 ? normalize(ls->P - klight->co) : -ray_D; + if (klight->spot.is_sphere) { + const float d_sq = len_squared(ray_P - klight->co); + ls->pdf = sphere_light_pdf(d_sq, r_sq, N, ray_D, path_flag); + ls->Ng = normalize(ls->P - klight->co); + } + else { + if (ls->t != FLT_MAX) { + const float3 lightN = normalize(ray_P - klight->co); + const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f; + ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ray_D, ls->t); + } + else { + ls->pdf = 0.0f; + } + ls->Ng = -ray_D; + } + /* Texture coordinates. */ const Transform itfm = klight->itfm; const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng)); /* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */ ls->u = uv.y; ls->v = 1.0f - uv.x - uv.y; - if (ls->t == FLT_MAX) { - ls->pdf = 0.0f; - } - else { - ls->pdf = point_light_pdf(len_squared(ray_P - klight->co), sqr(radius), N, ray_D, path_flag); - } - return true; } @@ -175,16 +205,25 @@ ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelL point_to_centroid = safe_normalize_len(centroid - P, &dist_point_to_centroid); const float radius = klight->spot.radius; - if (dist_point_to_centroid > radius) { - /* Equivalent to a disk light with the same angular span. */ - cos_theta_u = cos_from_sin(radius / dist_point_to_centroid); - distance = dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f); + + if (klight->spot.is_sphere) { + if (dist_point_to_centroid > radius) { + /* Equivalent to a disk light with the same angular span. */ + cos_theta_u = cos_from_sin(radius / dist_point_to_centroid); + distance = dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f); + } + else { + /* Similar to background light. */ + cos_theta_u = -1.0f; + /* HACK: pack radiance scaling in the distance. */ + distance = one_float2() * radius / M_SQRT2_F; + } } else { - /* Similar to background light. */ - cos_theta_u = -1.0f; - /* HACK: pack radiance scaling in the distance. */ - distance = one_float2() * radius / M_SQRT2_F; + const float hypotenus = sqrtf(sqr(radius) + sqr(dist_point_to_centroid)); + cos_theta_u = dist_point_to_centroid / hypotenus; + + distance = make_float2(hypotenus, dist_point_to_centroid); } return true; diff --git a/intern/cycles/kernel/light/spot.h b/intern/cycles/kernel/light/spot.h index 10491b97541..0e6ea5b5669 100644 --- a/intern/cycles/kernel/light/spot.h +++ b/intern/cycles/kernel/light/spot.h @@ -43,82 +43,104 @@ ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight, const int shader_flags, ccl_private LightSample *ls) { - const float radius = klight->spot.radius; const float r_sq = sqr(klight->spot.radius); - const float3 center = klight->co; - - float3 lightN = P - center; + float3 lightN = P - klight->co; const float d_sq = len_squared(lightN); const float d = sqrtf(d_sq); lightN /= d; - float cos_theta; - ls->t = FLT_MAX; - if (d_sq > r_sq) { - const float one_minus_cos_half_spot_spread = 1.0f - klight->spot.cos_half_spot_angle; - const float one_minus_cos_half_angle = sin_sqr_to_one_minus_cos(r_sq / d_sq); + ls->eval_fac = klight->spot.eval_fac; - if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) { - /* Sample visible part of the sphere. */ - ls->D = sample_uniform_cone(-lightN, one_minus_cos_half_angle, rand, &cos_theta, &ls->pdf); - } - else { - /* Sample spread cone. */ - ls->D = sample_uniform_cone( - -klight->spot.dir, one_minus_cos_half_spot_spread, rand, &cos_theta, &ls->pdf); + if (klight->spot.is_sphere) { + /* Spherical light geometry. */ + float cos_theta; + 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_angle = sin_sqr_to_one_minus_cos(r_sq / d_sq); - if (!ray_sphere_intersect(P, ls->D, 0.0f, FLT_MAX, center, radius, &ls->P, &ls->t)) { - /* Sampled direction does not intersect with the light. */ - return false; + if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) { + /* Sample visible part of the sphere. */ + ls->D = sample_uniform_cone(-lightN, one_minus_cos_half_angle, rand, &cos_theta, &ls->pdf); + } + else { + /* Sample spread cone. */ + ls->D = sample_uniform_cone( + -klight->spot.dir, one_minus_cos_half_spot_spread, rand, &cos_theta, &ls->pdf); + + if (!ray_sphere_intersect( + P, ls->D, 0.0f, FLT_MAX, klight->co, klight->spot.radius, &ls->P, &ls->t)) + { + /* Sampled direction does not intersect with the light. */ + return false; + } } } - } - else { - const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION); - if (has_transmission) { - ls->D = sample_uniform_sphere(rand); - ls->pdf = M_1_2PI_F * 0.5f; + else { + /* Inside sphere. */ + const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION); + if (has_transmission) { + ls->D = sample_uniform_sphere(rand); + ls->pdf = M_1_2PI_F * 0.5f; + } + else { + sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf); + } + cos_theta = -dot(ls->D, lightN); + } + + /* Attenuation. */ + const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); + if (d_sq > r_sq) { + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + } + if (!in_volume_segment && ls->eval_fac == 0.0f) { + return false; + } + + if (ls->t == FLT_MAX) { + /* Law of cosines. */ + ls->t = d * cos_theta - + copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq); + ls->P = P + ls->D * ls->t; } else { - sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf); + /* Already computed when sampling the spread cone. */ } - cos_theta = -dot(ls->D, lightN); - } - if (ls->t == FLT_MAX) { - /* Law of cosines. */ - ls->t = d * cos_theta - - copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq); - ls->P = P + ls->D * ls->t; - } - else { - /* Already computed when sampling the spread cone. */ - } - - const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); - ls->eval_fac = klight->spot.eval_fac; - if (d_sq > r_sq) { - ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); - } - - if (!in_volume_segment && ls->eval_fac == 0.0f) { - return false; - } - - if (r_sq == 0) { - /* Use intensity instead of radiance when the radius is zero. */ - ls->eval_fac /= sqr(ls->t); - /* `ls->Ng` is not well-defined when the radius is zero, use the incoming direction instead. */ - ls->Ng = -ls->D; - } - else { - ls->Ng = normalize(ls->P - center); /* Remap sampled point onto the sphere to prevent precision issues with small radius. */ - ls->P = ls->Ng * radius + center; - } + ls->Ng = normalize(ls->P - klight->co); + ls->P = ls->Ng * klight->spot.radius + klight->co; - spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); + /* Texture coordinates. */ + spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); + } + else { + /* Point light with ad-hoc radius based on oriented disk. */ + ls->P = klight->co; + if (r_sq > 0.0f) { + ls->P += disk_light_sample(lightN, rand) * klight->spot.radius; + } + + ls->D = normalize_len(ls->P - P, &ls->t); + ls->Ng = -ls->D; + + /* Attenuation. */ + const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + if (!in_volume_segment && ls->eval_fac == 0.0f) { + return false; + } + + /* PDF. */ + const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f; + ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ls->D, ls->t); + + /* Texture coordinates. */ + spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); + } return true; } @@ -146,35 +168,40 @@ ccl_device_forceinline void spot_light_mnee_sample_update(const ccl_global Kerne { ls->D = normalize_len(ls->P - P, &ls->t); - const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); ls->eval_fac = klight->spot.eval_fac; const float radius = klight->spot.radius; + bool use_attenuation = true; - if (radius > 0) { + if (klight->spot.is_sphere) { const float d_sq = len_squared(P - klight->co); const float r_sq = sqr(radius); const float t_sq = sqr(ls->t); - ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ls->D, path_flag); - /* NOTE : preserve pdf in area measure. */ - ls->pdf *= 0.5f * fabsf(d_sq - r_sq - t_sq) / (radius * ls->t * t_sq); + 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) * + jacobian_solid_angle_to_area; ls->Ng = normalize(ls->P - klight->co); - if (d_sq > r_sq) { - ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); - } + use_attenuation = (d_sq > r_sq); } else { + /* NOTE : preserve pdf in area measure. */ + ls->pdf = ls->eval_fac * 4.0f * M_PI_F; + ls->Ng = -ls->D; - - ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); - - /* PDF does not change. */ } + /* Attenuation. */ + const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); + if (use_attenuation) { + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + } + + /* Texture coordinates. */ spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); } @@ -199,22 +226,37 @@ ccl_device_inline bool spot_light_sample_from_intersection( const uint32_t path_flag, ccl_private LightSample *ccl_restrict ls) { - const float d_sq = len_squared(ray_P - klight->co); const float r_sq = sqr(klight->spot.radius); + const float d_sq = len_squared(ray_P - klight->co); - ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ray_D, path_flag); - - const float3 local_ray = spot_light_to_local(&klight->spot, -ray_D); ls->eval_fac = klight->spot.eval_fac; - if (d_sq > r_sq) { + + 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->Ng = normalize(ls->P - klight->co); + } + else { + if (ls->t != FLT_MAX) { + const float3 lightN = normalize(ray_P - klight->co); + const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f; + ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ray_D, ls->t); + } + else { + ls->pdf = 0.0f; + } + ls->Ng = -ray_D; + } + + /* Attenuation. */ + const float3 local_ray = spot_light_to_local(&klight->spot, -ray_D); + if (!klight->spot.is_sphere || d_sq > r_sq) { ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); } if (ls->eval_fac == 0) { return false; } - ls->Ng = r_sq > 0 ? normalize(ls->P - klight->co) : -ray_D; - + /* Texture coordinates. */ spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); return true; @@ -232,16 +274,30 @@ ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLi const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &dist_point_to_centroid); const float radius = klight->spot.radius; - cos_theta_u = (dist_point_to_centroid > radius) ? cos_from_sin(radius / dist_point_to_centroid) : - -1.0f; - if (in_volume_segment) { - return true; + if (klight->spot.is_sphere) { + cos_theta_u = (dist_point_to_centroid > radius) ? + cos_from_sin(radius / dist_point_to_centroid) : + -1.0f; + + if (in_volume_segment) { + return true; + } + + distance = (dist_point_to_centroid > radius) ? + dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f) : + one_float2() * radius / M_SQRT2_F; } + else { + const float hypotenus = sqrtf(sqr(radius) + sqr(dist_point_to_centroid)); + cos_theta_u = dist_point_to_centroid / hypotenus; - distance = (dist_point_to_centroid > radius) ? - dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f) : - one_float2() * radius / M_SQRT2_F; + if (in_volume_segment) { + return true; + } + + distance = make_float2(hypotenus, dist_point_to_centroid); + } point_to_centroid = point_to_centroid_; return true; diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index eea1df19392..b50a0c192a1 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1369,7 +1369,7 @@ typedef struct KernelSpotLight { float half_cot_half_spot_angle; float inv_len_z; float spot_smooth; - float pad; + int is_sphere; } KernelSpotLight; /* PointLight is SpotLight with only radius and invarea being used. */ diff --git a/intern/cycles/scene/light.cpp b/intern/cycles/scene/light.cpp index a0e5e83dd09..16483f0b798 100644 --- a/intern/cycles/scene/light.cpp +++ b/intern/cycles/scene/light.cpp @@ -110,6 +110,8 @@ NODE_DEFINE(Light) SOCKET_INT(map_resolution, "Map Resolution", 0); SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f); + SOCKET_BOOLEAN(is_sphere, "Is Sphere", true); + SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F); SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f); @@ -1253,6 +1255,7 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce klights[light_index].co = light->get_co(); klights[light_index].spot.radius = radius; klights[light_index].spot.eval_fac = eval_fac; + klights[light_index].spot.is_sphere = light->get_is_sphere() && radius != 0.0f; } else if (light->light_type == LIGHT_DISTANT) { shader_id &= ~SHADER_AREA_LIGHT; diff --git a/intern/cycles/scene/light.h b/intern/cycles/scene/light.h index 5b89160b371..8d6c1eac2db 100644 --- a/intern/cycles/scene/light.h +++ b/intern/cycles/scene/light.h @@ -48,6 +48,8 @@ class Light : public Node { NODE_SOCKET_API(int, map_resolution) NODE_SOCKET_API(float, average_radiance) + NODE_SOCKET_API(bool, is_sphere) + NODE_SOCKET_API(float, spot_angle) NODE_SOCKET_API(float, spot_smooth) diff --git a/scripts/startup/bl_ui/properties_data_light.py b/scripts/startup/bl_ui/properties_data_light.py index 87e1902af18..472bf7d86f5 100644 --- a/scripts/startup/bl_ui/properties_data_light.py +++ b/scripts/startup/bl_ui/properties_data_light.py @@ -98,6 +98,7 @@ class DATA_PT_EEVEE_light(DataButtonsPanel, Panel): col.separator() if light.type in {'POINT', 'SPOT'}: + col.prop(light, "use_soft_falloff") col.prop(light, "shadow_soft_size", text="Radius") elif light.type == 'SUN': col.prop(light, "angle") diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 4bcde25d51f..d0f43d41bbc 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -2887,6 +2887,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + /* Keep point/spot light soft falloff for files created before 4.0. */ + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 0)) { + LISTBASE_FOREACH (Light *, light, &bmain->lights) { + if (light->type == LA_LOCAL || light->type == LA_SPOT) { + light->mode |= LA_USE_SOFT_FALLOFF; + } + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/draw/engines/eevee/eevee_lights.cc b/source/blender/draw/engines/eevee/eevee_lights.cc index 6c4c5fce831..b4fdc7bfa0f 100644 --- a/source/blender/draw/engines/eevee/eevee_lights.cc +++ b/source/blender/draw/engines/eevee/eevee_lights.cc @@ -191,6 +191,14 @@ static void eevee_light_setup(Object *ob, EEVEE_Light *evli) if ((la->type == LA_AREA) && ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) { evli->light_type = LAMPTYPE_AREA_ELLIPSE; } + else if (la->mode & LA_USE_SOFT_FALLOFF) { + if (la->type == LA_LOCAL) { + evli->light_type = LAMPTYPE_OMNI_DISK; + } + else if (la->type == LA_SPOT) { + evli->light_type = LAMPTYPE_SPOT_DISK; + } + } float shape_power = light_shape_radiance_get(la, evli); mul_v3_fl(evli->color, shape_power * la->energy); diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index 41b45cce082..b9a5cd1500a 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -447,7 +447,9 @@ typedef struct EEVEE_Light { float diff, spec, volume, volume_radius; } EEVEE_Light; -/* Special type for elliptic area lights, matches lamps_lib.glsl */ +/* Special type for elliptic area lights and point/spot disk lights, matches lights_lib.glsl */ +#define LAMPTYPE_OMNI_DISK 0.5f +#define LAMPTYPE_SPOT_DISK 2.5f #define LAMPTYPE_AREA_ELLIPSE 100.0f typedef struct EEVEE_Shadow { diff --git a/source/blender/draw/engines/eevee/eevee_shadows_cube.cc b/source/blender/draw/engines/eevee/eevee_shadows_cube.cc index 8f7479ba977..846d33c5f6a 100644 --- a/source/blender/draw/engines/eevee/eevee_shadows_cube.cc +++ b/source/blender/draw/engines/eevee/eevee_shadows_cube.cc @@ -189,11 +189,13 @@ void EEVEE_shadows_draw_cubemap(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, for (int j = 0; j < 6; j++) { /* Optimization: Only render the needed faces. */ /* Skip all but -Z face. */ - if (evli->light_type == LA_SPOT && j != 5 && spot_angle_fit_single_face(evli)) { + if ((evli->light_type == LA_SPOT || evli->light_type == LAMPTYPE_SPOT_DISK) && j != 5 && + spot_angle_fit_single_face(evli)) + { continue; } /* Skip +Z face. */ - if (evli->light_type != LA_LOCAL && j == 4) { + if (!(evli->light_type == LA_LOCAL || evli->light_type == LAMPTYPE_OMNI_DISK) && j == 4) { continue; } /* TODO(fclem): some cube sides can be invisible in the main views. Cull them. */ diff --git a/source/blender/draw/engines/eevee/shaders/lights_lib.glsl b/source/blender/draw/engines/eevee/shaders/lights_lib.glsl index 876124ff932..041ccee29f6 100644 --- a/source/blender/draw/engines/eevee/shaders/lights_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/lights_lib.glsl @@ -86,10 +86,17 @@ uniform depth2DArrayShadow shadowCascadeTexture; /** \name Shadow Functions * \{ */ -/* type */ -#define POINT 0.0 +/* Type. Consistent with DNA `Light::type`, except for `OMNI_DISK`/`SPOT_DISK` and `AREA_ELLIPSE`, + * which are reinterpreted from `light.mode` and `light.area_type`. The decimal numbers are chosen + * so that they can be exactly represented by float, and guarantee an increasing sequence. */ +#define OMNI_SPHERE 0.0 +#define OMNI_DISK 0.5 + #define SUN 1.0 -#define SPOT 2.0 + +#define SPOT_SPHERE 2.0 +#define SPOT_DISK 2.5 + #define AREA_RECT 4.0 /* Used to define the area light shape, doesn't directly correspond to a Blender light type. */ #define AREA_ELLIPSE 100.0 @@ -233,10 +240,10 @@ float spot_attenuation(LightData ld, vec3 l_vector) float light_attenuation(LightData ld, vec4 l_vector) { float vis = 1.0; - if (ld.l_type == SPOT) { + if (ld.l_type == SPOT_SPHERE || ld.l_type == SPOT_DISK) { vis *= spot_attenuation(ld, l_vector.xyz); } - if (ld.l_type >= SPOT) { + if (ld.l_type >= SPOT_SPHERE) { vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward)); } if (ld.l_type != SUN) { @@ -309,6 +316,11 @@ float light_visibility(LightData ld, vec3 P, vec4 l_vector) return light_shadowing(ld, P, l_atten); } +bool is_sphere_light(float type) +{ + return type == OMNI_SPHERE || type == SPOT_SPHERE; +} + float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector) { if (ld.l_type == AREA_RECT) { @@ -328,7 +340,7 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector) return ltc_evaluate_disk(N, V, mat3(1.0), points); } - else if (ld.l_type == POINT) { + else if (is_sphere_light(ld.l_type)) { if (l_vector.w < ld.l_radius) { /* Inside, treat as hemispherical light. */ return 1.0; @@ -343,6 +355,7 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector) } } else { + /* Sun light or omni disk light. */ float radius = ld.l_radius; radius /= (ld.l_type == SUN) ? 1.0 : l_vector.w; vec3 L = (ld.l_type == SUN) ? -ld.l_forward : (l_vector.xyz / l_vector.w); @@ -364,7 +377,7 @@ float light_specular(LightData ld, vec4 ltc_mat, vec3 N, vec3 V, vec4 l_vector) return ltc_evaluate_quad(corners, vec3(0.0, 0.0, 1.0)); } - else if (ld.l_type == POINT && l_vector.w < ld.l_radius) { + else if (is_sphere_light(ld.l_type) && l_vector.w < ld.l_radius) { /* Inside the sphere light, integrate over the hemisphere. */ return 1.0; } @@ -373,7 +386,7 @@ float light_specular(LightData ld, vec4 ltc_mat, vec3 N, vec3 V, vec4 l_vector) float radius_x = is_ellipse ? ld.l_sizex : ld.l_radius; float radius_y = is_ellipse ? ld.l_sizey : ld.l_radius; - if (ld.l_type == POINT) { + if (is_sphere_light(ld.l_type)) { /* The sine of the half-angle spanned by a sphere light is equal to the tangent of the * half-angle spanned by a disk light with the same radius. */ radius_x *= inversesqrt(1.0 - sqr(radius_x / l_vector.w)); @@ -381,10 +394,14 @@ float light_specular(LightData ld, vec4 ltc_mat, vec3 N, vec3 V, vec4 l_vector) } vec3 L = (ld.l_type == SUN) ? -ld.l_forward : l_vector.xyz; - vec3 Px = ld.l_right; - vec3 Py = ld.l_up; + vec3 Px, Py; - if (ld.l_type == SPOT || ld.l_type == POINT) { + if (ld.l_type == SUN || ld.l_type == AREA_ELLIPSE) { + Px = ld.l_right; + Py = ld.l_up; + } + else { + /* Omni and spot lights. */ make_orthonormal_basis(l_vector.xyz / l_vector.w, Px, Py); } diff --git a/source/blender/draw/engines/eevee_next/eevee_light.cc b/source/blender/draw/engines/eevee_next/eevee_light.cc index ed22ab6deab..26ee49d5e45 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.cc +++ b/source/blender/draw/engines/eevee_next/eevee_light.cc @@ -22,16 +22,18 @@ namespace blender::eevee { /** \name LightData * \{ */ -static eLightType to_light_type(short blender_light_type, short blender_area_type) +static eLightType to_light_type(short blender_light_type, + short blender_area_type, + bool use_soft_falloff) { switch (blender_light_type) { default: case LA_LOCAL: - return LIGHT_POINT; + return use_soft_falloff ? LIGHT_OMNI_DISK : LIGHT_OMNI_SPHERE; case LA_SUN: return LIGHT_SUN; case LA_SPOT: - return LIGHT_SPOT; + return use_soft_falloff ? LIGHT_SPOT_DISK : LIGHT_SPOT_SPHERE; case LA_AREA: return ELEM(blender_area_type, LA_AREA_DISK, LA_AREA_ELLIPSE) ? LIGHT_ELLIPSE : LIGHT_RECT; } @@ -76,7 +78,7 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold) this->power[LIGHT_SPECULAR] = la->spec_fac * shape_power; this->power[LIGHT_VOLUME] = la->volume_fac * point_power; - eLightType new_type = to_light_type(la->type, la->area_shape); + eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF); if (assign_if_different(this->type, new_type)) { shadow_discard_safe(shadows); } diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index e0c070e5164..ec08d71c7cc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -712,8 +712,13 @@ BLI_STATIC_ASSERT_ALIGN(LightCullingData, 16) enum eLightType : uint32_t { LIGHT_SUN = 0u, LIGHT_SUN_ORTHO = 1u, - LIGHT_POINT = 10u, - LIGHT_SPOT = 11u, + /* Point light. */ + LIGHT_OMNI_SPHERE = 10u, + LIGHT_OMNI_DISK = 11u, + /* Spot light. */ + LIGHT_SPOT_SPHERE = 12u, + LIGHT_SPOT_DISK = 13u, + /* Area light. */ LIGHT_RECT = 20u, LIGHT_ELLIPSE = 21u }; @@ -730,9 +735,19 @@ static inline bool is_area_light(eLightType type) return type >= LIGHT_RECT; } +static inline bool is_spot_light(eLightType type) +{ + return type == LIGHT_SPOT_SPHERE || type == LIGHT_SPOT_DISK; +} + +static inline bool is_sphere_light(eLightType type) +{ + return type == LIGHT_SPOT_SPHERE || type == LIGHT_OMNI_SPHERE; +} + static inline bool is_sun_light(eLightType type) { - return type < LIGHT_POINT; + return type < LIGHT_OMNI_SPHERE; } struct LightData { diff --git a/source/blender/draw/engines/eevee_next/eevee_shadow.cc b/source/blender/draw/engines/eevee_next/eevee_shadow.cc index 3c097449b45..27545efa8f8 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shadow.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shadow.cc @@ -226,7 +226,7 @@ void ShadowPunctual::sync(eLightType light_type, float max_distance, float softness_factor) { - if (light_type == LIGHT_SPOT) { + if (is_spot_light(light_type)) { tilemaps_needed_ = (cone_aperture > DEG2RADF(90.0f)) ? 5 : 1; } else if (is_area_light(light_type)) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl index 15e8f576098..b5d12c88fc9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl @@ -33,7 +33,8 @@ void main() Sphere sphere; switch (light.type) { - case LIGHT_SPOT: + case LIGHT_SPOT_SPHERE: + case LIGHT_SPOT_DISK: /* Only for < ~170 degree Cone due to plane extraction precision. */ if (light.spot_tan < 10.0) { Pyramid pyramid = shape_pyramid_non_oblique( @@ -47,7 +48,8 @@ void main() } case LIGHT_RECT: case LIGHT_ELLIPSE: - case LIGHT_POINT: + case LIGHT_OMNI_SPHERE: + case LIGHT_OMNI_DISK: sphere.center = light._position; sphere.radius = light.influence_radius_max; break; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl index 4e20787d651..b6f7b8e32fe 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl @@ -158,7 +158,8 @@ void main() bool intersect_tile = intersect(tile, sphere); switch (light.type) { - case LIGHT_SPOT: + case LIGHT_SPOT_SPHERE: + case LIGHT_SPOT_DISK: /* Only for < ~170 degree Cone due to plane extraction precision. */ if (light.spot_tan < 10.0) { Pyramid pyramid = shape_pyramid_non_oblique( diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl index 2339deae622..4f8762975a3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl @@ -107,7 +107,7 @@ float light_attenuation_common(LightData light, const bool is_directional, vec3 if (is_directional) { return 1.0; } - if (light.type == LIGHT_SPOT) { + if (is_spot_light(light.type)) { return light_spot_attenuation(light, L); } if (is_area_light(light.type)) { @@ -183,7 +183,7 @@ float light_sphere_disk_radius(float sphere_radius, float distance_to_sphere) float light_ltc( sampler2DArray utility_tx, LightData light, vec3 N, vec3 V, LightVector lv, vec4 ltc_mat) { - if (light.type == LIGHT_POINT && lv.dist < light._radius) { + if (is_sphere_light(light.type) && lv.dist < light._radius) { /* Inside the sphere light, integrate over the hemisphere. */ return 1.0; } @@ -213,12 +213,20 @@ float light_ltc( make_orthonormal_basis(lv.L, Px, Py); } - vec2 size = vec2(light._area_size_x, light._area_size_y); - if (light.type == LIGHT_POINT) { - /* The sine of the half-angle spanned by a sphere light is equal to the tangent of the - * half-angle spanned by a disk light with the same radius. */ + vec2 size; + if (is_sphere_light(light.type)) { + /* Spherical omni or spot light. */ size = vec2(light_sphere_disk_radius(light._radius, lv.dist)); } + else if (light.type == LIGHT_OMNI_DISK || light.type == LIGHT_SPOT_DISK) { + /* View direction-aligned disk. */ + size = vec2(light._radius); + } + else { + /* Sun light and elliptical area light. */ + size = vec2(light._area_size_x, light._area_size_y); + } + vec3 points[3]; points[0] = Px * -size.x + Py * -size.y; points[1] = Px * size.x + Py * -size.y; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl index 7be13a72295..8e4116877f9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl @@ -93,7 +93,7 @@ void shadow_tag_usage_tilemap_punctual( if (dist_to_light > light.influence_radius_max) { return; } - if (light.type == LIGHT_SPOT) { + if (is_spot_light(light.type)) { /* Early out if out of cone. */ float angle_tan = length(lP.xy / dist_to_light); if (angle_tan > light.spot_tan) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl index 99cb7f19c03..2b08ebd5f00 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl @@ -331,7 +331,13 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, /* Disk rotated towards light vector. */ vec3 right, up; make_orthonormal_basis(L, right, up); - random_2d *= light_sphere_disk_radius(light._radius, dist); + if (is_sphere_light(light.type)) { + /* FIXME(weizhen): this is not well-defined when `dist < light._radius`. */ + random_2d *= light_sphere_disk_radius(light._radius, dist); + } + else { + random_2d *= light._radius; + } random_2d *= light.shadow_shape_scale_or_angle; vec3 point_on_light_shape = right * random_2d.x + up * random_2d.y; diff --git a/source/blender/makesdna/DNA_light_defaults.h b/source/blender/makesdna/DNA_light_defaults.h index 8122f280c92..cff2cf7b5a9 100644 --- a/source/blender/makesdna/DNA_light_defaults.h +++ b/source/blender/makesdna/DNA_light_defaults.h @@ -24,7 +24,7 @@ .energy_deprecated = 10.0f, \ .spotsize = DEG2RADF(45.0f), \ .spotblend = 0.15f, \ - .mode = LA_SHADOW, \ + .mode = LA_SHADOW | LA_USE_SOFT_FALLOFF, \ .clipsta = 0.05f, \ .clipend = 40.0f, \ .bias = 1.0f, \ diff --git a/source/blender/makesdna/DNA_light_types.h b/source/blender/makesdna/DNA_light_types.h index b9863f30791..6b20f307421 100644 --- a/source/blender/makesdna/DNA_light_types.h +++ b/source/blender/makesdna/DNA_light_types.h @@ -139,6 +139,7 @@ enum { // LA_SHOW_SHADOW_BOX = 1 << 18, LA_SHAD_CONTACT = 1 << 19, LA_CUSTOM_ATTENUATION = 1 << 20, + LA_USE_SOFT_FALLOFF = 1 << 21, }; /** #Light::falloff_type */ diff --git a/source/blender/makesrna/intern/rna_light.cc b/source/blender/makesrna/intern/rna_light.cc index b3791cbfb1f..227881573ca 100644 --- a/source/blender/makesrna/intern/rna_light.cc +++ b/source/blender/makesrna/intern/rna_light.cc @@ -346,6 +346,15 @@ static void rna_def_point_light(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Point Light", "Omnidirectional point Light"); RNA_def_struct_ui_icon(srna, ICON_LIGHT_POINT); + PropertyRNA *prop; + prop = RNA_def_property(srna, "use_soft_falloff", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "mode", LA_USE_SOFT_FALLOFF); + RNA_def_property_ui_text( + prop, + "Soft Falloff", + "Apply falloff to avoid sharp edges when the light geometry intersects with other objects"); + RNA_def_property_update(prop, 0, "rna_Light_draw_update"); + rna_def_light_energy(srna, LA_LOCAL); rna_def_light_shadow(srna, false); } @@ -442,6 +451,14 @@ static void rna_def_spot_light(BlenderRNA *brna) "Show Cone", "Display transparent cone in 3D view to visualize which objects are contained in it"); RNA_def_property_update(prop, 0, "rna_Light_draw_update"); + + prop = RNA_def_property(srna, "use_soft_falloff", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "mode", LA_USE_SOFT_FALLOFF); + RNA_def_property_ui_text( + prop, + "Soft Falloff", + "Apply falloff to avoid sharp edges when the light geometry intersects with other objects"); + RNA_def_property_update(prop, 0, "rna_Light_draw_update"); } static void rna_def_sun_light(BlenderRNA *brna)