diff --git a/intern/cycles/kernel/light/light.h b/intern/cycles/kernel/light/light.h index 87596a0abef..b3f5b3e9b41 100644 --- a/intern/cycles/kernel/light/light.h +++ b/intern/cycles/kernel/light/light.h @@ -147,7 +147,7 @@ ccl_device_inline bool light_sample(KernelGlobals kg, ls->eval_fac = 1.0f; } else if (type == LIGHT_SPOT) { - if (!spot_light_sample(klight, rand, P, ls)) { + if (!spot_light_sample(klight, rand, P, N, shader_flags, ls)) { return false; } } @@ -469,7 +469,7 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg, ls->group = lamp_lightgroup(kg, lamp); if (type == LIGHT_SPOT) { - if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) { + if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, N, path_flag, ls)) { return false; } } diff --git a/intern/cycles/kernel/light/point.h b/intern/cycles/kernel/light/point.h index a4dbd1850fe..bc41dea5fd1 100644 --- a/intern/cycles/kernel/light/point.h +++ b/intern/cycles/kernel/light/point.h @@ -44,7 +44,7 @@ ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight, ls->P = P + ls->D * ls->t; - ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea; + 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); @@ -100,7 +100,7 @@ ccl_device_forceinline void point_light_mnee_sample_update(const ccl_global Kern ls->Ng = normalize(ls->P - klight->co); } else { - ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea; + ls->eval_fac = klight->spot.eval_fac; ls->Ng = -ls->D; @@ -136,7 +136,7 @@ ccl_device_inline bool point_light_sample_from_intersection( const uint32_t path_flag, ccl_private LightSample *ccl_restrict ls) { - ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea; + ls->eval_fac = klight->spot.eval_fac; const float radius = klight->spot.radius; diff --git a/intern/cycles/kernel/light/sample.h b/intern/cycles/kernel/light/sample.h index 96d6474e7af..afc4537c671 100644 --- a/intern/cycles/kernel/light/sample.h +++ b/intern/cycles/kernel/light/sample.h @@ -381,7 +381,7 @@ ccl_device_forceinline void light_sample_update(KernelGlobals kg, point_light_mnee_sample_update(klight, ls, P, N, path_flag); } else if (ls->type == LIGHT_SPOT) { - spot_light_mnee_sample_update(klight, ls, P); + spot_light_mnee_sample_update(klight, ls, P, N, path_flag); } else if (ls->type == LIGHT_AREA) { area_light_mnee_sample_update(klight, ls, P); diff --git a/intern/cycles/kernel/light/spot.h b/intern/cycles/kernel/light/spot.h index ca9fe5cf34a..57430e79992 100644 --- a/intern/cycles/kernel/light/spot.h +++ b/intern/cycles/kernel/light/spot.h @@ -8,96 +8,187 @@ CCL_NAMESPACE_BEGIN -ccl_device float spot_light_attenuation(const ccl_global KernelSpotLight *spot, float3 ray) +/* Transform vector to spot light's local coordinate system. */ +ccl_device float3 spot_light_to_local(const ccl_global KernelSpotLight *spot, const float3 ray) { - const float3 scaled_ray = safe_normalize(make_float3(dot(ray, spot->scaled_axis_u), - dot(ray, spot->scaled_axis_v), - dot(ray, spot->dir * spot->inv_len_z))); + return safe_normalize(make_float3(dot(ray, spot->scaled_axis_u), + dot(ray, spot->scaled_axis_v), + dot(ray, spot->dir * spot->inv_len_z))); +} - return smoothstepf((scaled_ray.z - spot->cos_half_spot_angle) * spot->spot_smooth); +/* Compute spot light attenuation of a ray given in local coordinate system. */ +ccl_device float spot_light_attenuation(const ccl_global KernelSpotLight *spot, const float3 ray) +{ + return smoothstepf((ray.z - spot->cos_half_spot_angle) * spot->spot_smooth); +} + +ccl_device void spot_light_uv(const float3 ray, + const float half_cot_half_spot_angle, + ccl_private float *u, + ccl_private float *v) +{ + /* Ensures that the spot light projects the full image regarless of the spot angle. */ + const float factor = half_cot_half_spot_angle / ray.z; + + /* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */ + *u = ray.y * factor + 0.5f; + *v = -(ray.x + ray.y) * factor; } template ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight, const float2 rand, const float3 P, + const float3 N, + const int shader_flags, ccl_private LightSample *ls) { - ls->P = klight->co; + const float radius = klight->spot.radius; + const float r_sq = sqr(klight->spot.radius); const float3 center = klight->co; - const float radius = klight->spot.radius; - /* disk oriented normal */ - const float3 lightN = normalize(P - center); - ls->P = center; - if (radius > 0.0f) { - /* disk light */ - ls->P += disk_light_sample(lightN, rand) * radius; + float3 lightN = P - center; + 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); + + if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) { + /* Sample visible part of the sphere. */ + sample_uniform_cone_concentric( + -lightN, one_minus_cos_half_angle, rand, &cos_theta, &ls->D, &ls->pdf); + } + else { + /* Sample spread cone. */ + sample_uniform_cone_concentric( + -klight->spot.dir, one_minus_cos_half_spot_spread, rand, &cos_theta, &ls->D, &ls->pdf); + + 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; + } + } + } + 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 { + sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf); + } + cos_theta = -dot(ls->D, lightN); } - const float invarea = klight->spot.invarea; - ls->pdf = invarea; + 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. */ + } - ls->D = normalize_len(ls->P - P, &ls->t); - /* we set the light normal to the outgoing direction to support texturing */ - ls->Ng = -ls->D; + 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); + } - ls->eval_fac = (0.25f * M_1_PI_F) * invarea; - - /* spot light attenuation */ - ls->eval_fac *= spot_light_attenuation(&klight->spot, -ls->D); if (!in_volume_segment && ls->eval_fac == 0.0f) { return false; } - float2 uv = map_to_sphere(ls->Ng); - ls->u = uv.x; - ls->v = uv.y; + 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; + } + + spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); - ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t); return true; } +ccl_device_forceinline float spot_light_pdf(const float cos_half_spread, + const float d_sq, + const float r_sq, + const float3 N, + const float3 D, + 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); + } + + const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION); + return has_transmission ? M_1_2PI_F * 0.5f : pdf_cos_hemisphere(N, D); +} + ccl_device_forceinline void spot_light_mnee_sample_update(const ccl_global KernelLight *klight, ccl_private LightSample *ls, - const float3 P) + const float3 P, + const float3 N, + const uint32_t path_flag) { ls->D = normalize_len(ls->P - P, &ls->t); - ls->Ng = -ls->D; - float2 uv = map_to_sphere(ls->Ng); - ls->u = uv.x; - ls->v = uv.y; + const float3 local_ray = spot_light_to_local(&klight->spot, -ls->D); + ls->eval_fac = klight->spot.eval_fac; - float invarea = klight->spot.invarea; - ls->eval_fac = (0.25f * M_1_PI_F) * invarea; - /* NOTE : preserve pdf in area measure. */ - ls->pdf = invarea; + const float radius = klight->spot.radius; - /* spot light attenuation */ - ls->eval_fac *= spot_light_attenuation(&klight->spot, ls->Ng); + if (radius > 0) { + 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); + + ls->Ng = normalize(ls->P - klight->co); + + if (d_sq > r_sq) { + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + } + } + else { + ls->Ng = -ls->D; + + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + + /* PDF does not change. */ + } + + spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); } ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight, const ccl_private Ray *ccl_restrict ray, ccl_private float *t) { - /* Spot/Disk light. */ - const float3 lightP = klight->co; - const float radius = klight->spot.radius; - if (radius == 0.0f) { - return false; - } - /* disk oriented normal */ - const float3 lightN = normalize(ray->P - lightP); /* One sided. */ - if (dot(ray->D, lightN) >= 0.0f) { + if (dot(ray->D, ray->P - klight->co) >= 0.0f) { return false; } - float3 P; - return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t); + return point_light_intersect(klight, ray, t); } ccl_device_inline bool spot_light_sample_from_intersection( @@ -105,35 +196,27 @@ ccl_device_inline bool spot_light_sample_from_intersection( ccl_private const Intersection *ccl_restrict isect, const float3 ray_P, const float3 ray_D, + const float3 N, + const uint32_t path_flag, ccl_private LightSample *ccl_restrict ls) { - /* the normal of the oriented disk */ - const float3 lightN = normalize(ray_P - klight->co); - /* We set the light normal to the outgoing direction to support texturing. */ - ls->Ng = -ls->D; + const float d_sq = len_squared(ray_P - klight->co); + const float r_sq = sqr(klight->spot.radius); - float invarea = klight->spot.invarea; - ls->eval_fac = (0.25f * M_1_PI_F) * invarea; - ls->pdf = invarea; + ls->pdf = spot_light_pdf(klight->spot.cos_half_spot_angle, d_sq, r_sq, N, ray_D, path_flag); - /* spot light attenuation */ - ls->eval_fac *= spot_light_attenuation(&klight->spot, -ls->D); - - if (ls->eval_fac == 0.0f) { + const float3 local_ray = spot_light_to_local(&klight->spot, -ray_D); + ls->eval_fac = klight->spot.eval_fac; + if (d_sq > r_sq) { + ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray); + } + if (ls->eval_fac == 0) { return false; } - float2 uv = map_to_sphere(ls->Ng); - ls->u = uv.x; - ls->v = uv.y; + ls->Ng = r_sq > 0 ? normalize(ls->P - klight->co) : -ray_D; - /* compute pdf */ - if (ls->t != FLT_MAX) { - ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t); - } - else { - ls->pdf = 0.f; - } + spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle, &ls->u, &ls->v); return true; } @@ -146,18 +229,20 @@ ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLi ccl_private float2 &distance, ccl_private float3 &point_to_centroid) { - float min_distance; - const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance); + float dist_point_to_centroid; + const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &dist_point_to_centroid); const float radius = klight->spot.radius; - const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance)); - cos_theta_u = min_distance / hypotenus; + 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 = make_float2(hypotenus, min_distance); + 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; point_to_centroid = point_to_centroid_; return true; diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 3366fd331a4..1d8a956aeac 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1353,12 +1353,13 @@ typedef struct KernelSpotLight { packed_float3 scaled_axis_u; float radius; packed_float3 scaled_axis_v; - float invarea; + float eval_fac; packed_float3 dir; float cos_half_spot_angle; + float half_cot_half_spot_angle; float inv_len_z; float spot_smooth; - float pad[2]; + float pad; } 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 12cfdae4eb1..529fe2ca947 100644 --- a/intern/cycles/scene/light.cpp +++ b/intern/cycles/scene/light.cpp @@ -1210,22 +1210,22 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce klights[light_index].strength[1] = light->strength.y; klights[light_index].strength[2] = light->strength.z; - if (light->light_type == LIGHT_POINT) { + if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) { shader_id &= ~SHADER_AREA_LIGHT; float radius = light->size; - /* TODO: `invarea` was used for disk sampling, with the current solid angle sampling this - * becomes unnecessary. We could store `eval_fac` instead, but currently it shares the same - * #KernelSpotLight type with #LIGHT_SPOT, so keep it know until refactor for spot light. */ - float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : + + float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_4PI_F * radius * radius) : 1.0f; + /* Convert radiant flux to radiance or radiant intensity. */ + float eval_fac = (radius > 0) ? invarea * M_1_PI_F : 0.25f * M_1_PI_F; if (light->use_mis && radius > 0.0f) shader_id |= SHADER_USE_MIS; klights[light_index].co = co; klights[light_index].spot.radius = radius; - klights[light_index].spot.invarea = invarea; + klights[light_index].spot.eval_fac = eval_fac; } else if (light->light_type == LIGHT_DISTANT) { shader_id &= ~SHADER_AREA_LIGHT; @@ -1306,9 +1306,7 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce klights[light_index].area.tan_half_spread = tan_half_spread; klights[light_index].area.normalize_spread = normalize_spread; } - else if (light->light_type == LIGHT_SPOT) { - shader_id &= ~SHADER_AREA_LIGHT; - + if (light->light_type == LIGHT_SPOT) { /* Scale axes to accommodate non-uniform scaling. */ float3 scaled_axis_u = light->axisu / len_squared(light->axisu); float3 scaled_axis_v = light->axisv / len_squared(light->axisv); @@ -1316,22 +1314,14 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce /* Keep direction normalized. */ float3 dir = safe_normalize_len(light->dir, &len_z); - float radius = light->size; - float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : - 1.0f; 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); - if (light->use_mis && radius > 0.0f) - shader_id |= SHADER_USE_MIS; - - klights[light_index].co = co; klights[light_index].spot.scaled_axis_u = scaled_axis_u; - klights[light_index].spot.radius = radius; klights[light_index].spot.scaled_axis_v = scaled_axis_v; - klights[light_index].spot.invarea = invarea; klights[light_index].spot.dir = 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.inv_len_z = 1.0f / len_z; klights[light_index].spot.spot_smooth = spot_smooth; } diff --git a/intern/cycles/scene/light.h b/intern/cycles/scene/light.h index 875c079ec9d..7aa1542a974 100644 --- a/intern/cycles/scene/light.h +++ b/intern/cycles/scene/light.h @@ -76,6 +76,7 @@ class Light : public Node { NODE_SOCKET_API(uint64_t, light_set_membership); NODE_SOCKET_API(uint64_t, shadow_set_membership); + /* Normalize power by the surface area of the light. */ NODE_SOCKET_API(bool, normalize) void tag_update(Scene *scene); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 0eb01476592..df25e248274 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -213,6 +213,37 @@ static void versioning_remove_microfacet_sharp_distribution(bNodeTree *ntree) } } +static void version_replace_texcoord_normal_socket(bNodeTree *ntree) +{ + /* The normal of a spot light was set to the incoming light direction, replace with the + * `Incoming` socket from the Geometry shader node. */ + bNode *geometry_node = nullptr; + bNode *transform_node = nullptr; + bNodeSocket *incoming_socket = nullptr; + bNodeSocket *vec_in_socket = nullptr; + bNodeSocket *vec_out_socket = nullptr; + + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { + if (link->fromnode->type == SH_NODE_TEX_COORD && STREQ(link->fromsock->identifier, "Normal")) { + if (geometry_node == nullptr) { + geometry_node = nodeAddStaticNode(nullptr, ntree, SH_NODE_NEW_GEOMETRY); + incoming_socket = nodeFindSocket(geometry_node, SOCK_OUT, "Incoming"); + + transform_node = nodeAddStaticNode(nullptr, ntree, SH_NODE_VECT_TRANSFORM); + vec_in_socket = nodeFindSocket(transform_node, SOCK_IN, "Vector"); + vec_out_socket = nodeFindSocket(transform_node, SOCK_OUT, "Vector"); + + NodeShaderVectTransform *nodeprop = (NodeShaderVectTransform *)transform_node->storage; + nodeprop->type = SHD_VECT_TRANSFORM_TYPE_NORMAL; + + nodeAddLink(ntree, geometry_node, incoming_socket, transform_node, vec_in_socket); + } + nodeAddLink(ntree, transform_node, vec_out_socket, link->tonode, link->tosock); + nodeRemLink(ntree, link); + } + } +} + void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) { if (!MAIN_VERSION_ATLEAST(bmain, 400, 1)) { @@ -277,6 +308,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) act->frame_end = min_ff(act->frame_end, MAXFRAMEF); } } + + if (!MAIN_VERSION_ATLEAST(bmain, 400, 9)) { + LISTBASE_FOREACH (Light *, light, &bmain->lights) { + if (light->type == LA_SPOT && light->nodetree) { + version_replace_texcoord_normal_socket(light->nodetree); + } + } + } + /** * Versioning code until next subversion bump goes here. *