Lights: Option to use old point light falloff #117832

Merged
Brecht Van Lommel merged 14 commits from brecht/blender:point-sphere-light into blender-v4.1-release 2024-02-07 19:07:23 +01:00
27 changed files with 376 additions and 181 deletions

View File

@ -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")

View File

@ -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 */

View File

@ -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<false>(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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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. */

View File

@ -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;

View File

@ -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)

View File

@ -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")

View File

@ -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.

View File

@ -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);

View File

@ -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 {

View File

@ -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. */

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -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)) {

View File

@ -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;

View File

@ -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(

View File

@ -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) {
weizhen marked this conversation as resolved Outdated

You need to account for the spot case here.

You need to account for the spot case here.
/* 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;

View File

@ -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) {

View File

@ -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;

View File

@ -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, \

View File

@ -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 */

View File

@ -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)