From b6f27841e3e66db98311444c8a082b22ce3dce46 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 12 May 2023 15:46:39 +0200 Subject: [PATCH] Light Linking: Support shadow linking to emissive meshes This includes picking up a mesh emitter with the check for a fully blocked light by a blocker with default linking configuration. Implemented using an intersection loop, which might not have the best performance compared to intersect-all behavior, but allows to use some specialized for the shadow linking ray manipulations and object flags check. Ref #104972 --- .../integrator/intersect_dedicated_light.h | 108 +++++++++++-- intern/cycles/kernel/integrator/mnee.h | 1 + .../kernel/integrator/shade_dedicated_light.h | 153 ++++++++++++++---- .../cycles/kernel/integrator/shade_surface.h | 29 +++- intern/cycles/kernel/light/light.h | 21 ++- 5 files changed, 252 insertions(+), 60 deletions(-) diff --git a/intern/cycles/kernel/integrator/intersect_dedicated_light.h b/intern/cycles/kernel/integrator/intersect_dedicated_light.h index 337e93e5dcd..40e2aafdecc 100644 --- a/intern/cycles/kernel/integrator/intersect_dedicated_light.h +++ b/intern/cycles/kernel/integrator/intersect_dedicated_light.h @@ -14,19 +14,80 @@ CCL_NAMESPACE_BEGIN #ifdef __SHADOW_LINKING__ +# define SHADOW_LINK_MAX_INTERSECTION_COUNT 1024 + +/* Intersect mesh objects. + * + * Returns the total number of emissive surfaces hit, and the intersection contains a random + * intersected emitter to which the dedicated shadow ray is to eb traced. + * + * NOTE: Sets the ray tmax to the maximum intersection distance (past which no lights are to be + * considered for shadow linking). */ +ccl_device int shadow_linking_pick_mesh_intersection(KernelGlobals kg, + IntegratorState state, + ccl_private Ray *ccl_restrict ray, + ccl_private Intersection *ccl_restrict + linked_isect, + ccl_private uint *lcg_state, + int num_hits) +{ + /* The tmin will be offset, so store its current value and restore later on, allowing a separate + * light intersection loop starting from the actual ray origin. */ + const float old_tmin = ray->tmin; + + const uint visibility = path_state_ray_visibility(state); + + for (int i = 0; i < SHADOW_LINK_MAX_INTERSECTION_COUNT; i++) { + Intersection current_isect ccl_optional_struct_init; + current_isect.object = OBJECT_NONE; + current_isect.prim = PRIM_NONE; + + const bool hit = scene_intersect(kg, ray, visibility, ¤t_isect); + if (!hit) { + break; + } + + const uint64_t set_membership = + kernel_data_fetch(objects, current_isect.object).shadow_set_membership; + if (set_membership != LIGHT_LINK_MASK_ALL) { + ++num_hits; + + if ((linked_isect->prim == PRIM_NONE) && (lcg_step_float(lcg_state) < 1.0f / num_hits)) { + *linked_isect = current_isect; + } + } + + const uint blocker_set = kernel_data_fetch(objects, current_isect.object).blocker_shadow_set; + if (blocker_set == 0) { + /* Contribution from the lights past the default blocker is accumulated using the main path. + */ + ray->tmax = current_isect.t; + break; + } + + /* Move the ray forward. */ + ray->tmin = intersection_t_offset(current_isect.t); + } + + ray->tmin = old_tmin; + + return num_hits; +} + /* Pick a light for tracing a shadow ray for the shadow linking. * Picks a random light which is intersected by the given ray, and stores the intersection result. - * If no lights were hit false is returned. */ + * If no lights were hit false is returned. + * + * NOTE: Sets the ray tmax to the maximum intersection distance (past which no lights are to be + * considered for shadow linking). */ ccl_device bool shadow_linking_pick_light_intersection(KernelGlobals kg, IntegratorState state, - ccl_private const Ray *ccl_restrict ray, + ccl_private Ray *ccl_restrict ray, ccl_private Intersection *ccl_restrict - isect) + linked_isect) { uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - const int last_prim = INTEGRATOR_STATE(state, isect, prim); - const int last_object = INTEGRATOR_STATE(state, isect, object); const int last_type = INTEGRATOR_STATE(state, isect, type); uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_hash), @@ -34,19 +95,32 @@ ccl_device bool shadow_linking_pick_light_intersection(KernelGlobals kg, INTEGRATOR_STATE(state, path, sample), 0x68bc21eb); - /* The lights_intersect() has a "refining" behavior: it chooses intersection closer to the - * current intersection's distance. Hence initialize the fields which are accessed prior to - * recording an intersection. */ - isect->t = FLT_MAX; - isect->prim = PRIM_NONE; + /* Indicate that no intersection has been picked yet. */ + linked_isect->prim = PRIM_NONE; - // TODO: Support mesh emitters. + int num_hits = 0; - // TODO: Only if ray is not fully occluded. + // TODO: Only if there are emissive meshes in the scene? + // TODO: Only if the ray hits any light? As in, check that there is a light first, before + // tracing potentially expensive ray. + + num_hits = shadow_linking_pick_mesh_intersection( + kg, state, ray, linked_isect, &lcg_state, num_hits); + + // TODO: Only intersections up to the ray->tmax. Intersecting all will increase noise in cases + // when the light is behind knowingly opaque object. const int receiver_forward = light_link_receiver_forward(kg, state); - const int num_hits = lights_intersect_shadow_linked( - kg, ray, isect, last_prim, last_object, last_type, path_flag, receiver_forward, &lcg_state); + num_hits = lights_intersect_shadow_linked(kg, + ray, + linked_isect, + ray->self.prim, + ray->self.object, + last_type, + path_flag, + receiver_forward, + &lcg_state, + num_hits); if (num_hits == 0) { return false; @@ -72,6 +146,12 @@ ccl_device bool shadow_linking_intersect(KernelGlobals kg, IntegratorState state Ray ray ccl_optional_struct_init; integrator_state_read_ray(state, &ray); + ray.self.prim = INTEGRATOR_STATE(state, isect, prim); + ray.self.object = INTEGRATOR_STATE(state, isect, object); + ray.self.light_object = OBJECT_NONE; + ray.self.light_prim = PRIM_NONE; + ray.self.light = LAMP_NONE; + Intersection isect ccl_optional_struct_init; if (!shadow_linking_pick_light_intersection(kg, state, &ray, &isect)) { /* No light is hit, no need in the extra shadow ray for the direct light. */ diff --git a/intern/cycles/kernel/integrator/mnee.h b/intern/cycles/kernel/integrator/mnee.h index 102d34f2578..871382d2f78 100644 --- a/intern/cycles/kernel/integrator/mnee.h +++ b/intern/cycles/kernel/integrator/mnee.h @@ -483,6 +483,7 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg, if (!hit) break; + // TODO: Is the fetch needed here? The shade_surface simply reads isect->object. int hit_object = (projection_isect.object == OBJECT_NONE) ? kernel_data_fetch(prim_object, projection_isect.prim) : projection_isect.object; diff --git a/intern/cycles/kernel/integrator/shade_dedicated_light.h b/intern/cycles/kernel/integrator/shade_dedicated_light.h index 5a8d1cc5ada..57bc699c922 100644 --- a/intern/cycles/kernel/integrator/shade_dedicated_light.h +++ b/intern/cycles/kernel/integrator/shade_dedicated_light.h @@ -51,8 +51,6 @@ ccl_device void shadow_linking_setup_ray_from_intersection( ccl_private Ray *ccl_restrict ray, ccl_private const Intersection *ccl_restrict isect) { - kernel_assert(isect->type == PRIMITIVE_LAMP); - /* The ray->tmin follows the value configured at the surface bounce. * it is the same for the continued main path and for this shadow ray. There is no need to push * it forward here. */ @@ -65,13 +63,112 @@ ccl_device void shadow_linking_setup_ray_from_intersection( ray->self.object = INTEGRATOR_STATE(state, shadow_link, last_isect_object); ray->self.prim = INTEGRATOR_STATE(state, shadow_link, last_isect_prim); - // TODO: Support mesh lights. - ray->self.light_object = OBJECT_NONE; - ray->self.light_prim = PRIM_NONE; - ray->self.light = isect->prim; + if (isect->type == PRIMITIVE_LAMP) { + ray->self.light_object = OBJECT_NONE; + ray->self.light_prim = PRIM_NONE; + ray->self.light = isect->prim; + } + else { + ray->self.light_object = isect->object; + ray->self.light_prim = isect->prim; + ray->self.light = LAMP_NONE; + } } -ccl_device void shadow_linking_shade(KernelGlobals kg, IntegratorState state) +ccl_device bool shadow_linking_shade_light(KernelGlobals kg, + IntegratorState state, + ccl_private Ray &ccl_restrict ray, + ccl_private Intersection &ccl_restrict isect, + ccl_private ShaderData *emission_sd, + ccl_private Spectrum &ccl_restrict bsdf_spectrum, + ccl_private int &ccl_restrict light_group) +{ + LightSample ls ccl_optional_struct_init; + const bool use_light_sample = shadow_linking_light_sample_from_intersection(kg, isect, ray, &ls); + if (!use_light_sample) { + /* No light to be sampled, so no direct light contribution either. */ + return false; + } + + const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, ray.time); + if (is_zero(light_eval)) { + return false; + } + + const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); + if (!is_light_shader_visible_to_path(ls.shader, path_flag)) { + return false; + } + + /* MIS weighting. */ + float mis_weight = 1.0f; + if (!(path_flag & PATH_RAY_MIS_SKIP)) { + mis_weight = shadow_linking_light_sample_mis_weight(kg, state, path_flag, &ls, ray.P); + } + + bsdf_spectrum = light_eval * mis_weight * + INTEGRATOR_STATE(state, shadow_link, dedicated_light_weight); + + // TODO(: De-duplicate with the shade_surface. + // Possibly by ensuring ls->group is always assigned properly. + light_group = ls.type != LIGHT_BACKGROUND ? ls.group : kernel_data.background.lightgroup; + + return true; +} + +ccl_device bool shadow_linking_shade_surface_emission(KernelGlobals kg, + IntegratorState state, + ccl_private Ray &ccl_restrict ray, + ccl_private Intersection &ccl_restrict isect, + ccl_private ShaderData *emission_sd, + ccl_global float *ccl_restrict render_buffer, + ccl_private Spectrum &ccl_restrict + bsdf_spectrum, + ccl_private int &ccl_restrict light_group) +{ + const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); + + integrate_surface_shader_setup(kg, state, emission_sd); + +# ifdef __VOLUME__ + if (emission_sd->flag & SD_HAS_ONLY_VOLUME) { + return false; + } +# endif + + surface_shader_eval( + kg, state, emission_sd, render_buffer, path_flag); + surface_shader_prepare_closures(kg, state, emission_sd, path_flag); + + if ((emission_sd->flag & SD_EMISSION) == 0) { + return false; + } + + const Spectrum L = surface_shader_emission(emission_sd); + float mis_weight = 1.0f; + + const bool has_mis = !(path_flag & PATH_RAY_MIS_SKIP) && + (emission_sd->flag & + ((emission_sd->flag & SD_BACKFACING) ? SD_MIS_BACK : SD_MIS_FRONT)); + +# ifdef __HAIR__ + if (has_mis && (emission_sd->type & PRIMITIVE_TRIANGLE)) +# else + if (has_mis) +# endif + { + mis_weight = light_sample_mis_weight_forward_surface(kg, state, path_flag, emission_sd); + } + + bsdf_spectrum = L * mis_weight * INTEGRATOR_STATE(state, shadow_link, dedicated_light_weight); + light_group = object_lightgroup(kg, emission_sd->object); + + return true; +} + +ccl_device void shadow_linking_shade(KernelGlobals kg, + IntegratorState state, + ccl_global float *ccl_restrict render_buffer) { /* Read intersection from integrator state into local memory. */ Intersection isect ccl_optional_struct_init; @@ -81,39 +178,31 @@ ccl_device void shadow_linking_shade(KernelGlobals kg, IntegratorState state) Ray ray ccl_optional_struct_init; integrator_state_read_ray(state, &ray); - LightSample ls ccl_optional_struct_init; - const bool use_light_sample = shadow_linking_light_sample_from_intersection(kg, isect, ray, &ls); - if (!use_light_sample) { - /* No light to be sampled, so no direct light contribution either. */ - return; - } - ShaderDataCausticsStorage emission_sd_storage; ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage); - const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, ray.time); - if (is_zero(light_eval)) { - return; - } - const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - if (!is_light_shader_visible_to_path(ls.shader, path_flag)) { - return; - } + Spectrum bsdf_spectrum; + int light_group = LIGHTGROUP_NONE; - /* MIS weighting. */ - float mis_weight = 1.0f; - if (!(path_flag & PATH_RAY_MIS_SKIP)) { - mis_weight = shadow_linking_light_sample_mis_weight(kg, state, path_flag, &ls, ray.P); + if (isect.type == PRIMITIVE_LAMP) { + if (!shadow_linking_shade_light( + kg, state, ray, isect, emission_sd, bsdf_spectrum, light_group)) { + return; + } + } + else { + if (!shadow_linking_shade_surface_emission( + kg, state, ray, isect, emission_sd, render_buffer, bsdf_spectrum, light_group)) + { + return; + } } - - const Spectrum bsdf_spectrum = light_eval * mis_weight * - INTEGRATOR_STATE(state, shadow_link, dedicated_light_weight); shadow_linking_setup_ray_from_intersection(state, &ray, &isect); /* Branch off shadow kernel. */ IntegratorShadowState shadow_state = integrate_direct_light_shadow_init_common( - kg, state, &ls, &ray, bsdf_spectrum, 0); + kg, state, &ray, bsdf_spectrum, 0, light_group); /* No need to update the volume stack as the surface bounce already performed enter-exit check. */ @@ -138,12 +227,12 @@ ccl_device void shadow_linking_shade(KernelGlobals kg, IntegratorState state) ccl_device void integrator_shade_dedicated_light(KernelGlobals kg, IntegratorState state, - ccl_global float *ccl_restrict /*render_buffer*/) + ccl_global float *ccl_restrict render_buffer) { PROFILING_INIT(kg, PROFILING_SHADE_DEDICATED_LIGHT); #ifdef __SHADOW_LINKING__ - shadow_linking_shade(kg, state); + shadow_linking_shade(kg, state, render_buffer); /* Restore self-intersection check primitives in the main state before returning to the * intersect_closest() state. */ diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index f0b392563da..269f0e79164 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -109,13 +109,26 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, ccl_global float *ccl_restrict render_buffer) { - /* Light linking. */ +#ifdef __LIGHT_LINKING__ if (!light_link_object_match(kg, light_link_receiver_forward(kg, state), sd->object)) { return; } +#endif const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); +#ifdef __SHADOW_LINKING__ + /* Indirect emission of shadow-linked emissive surfaces is done via shadow rays to dedicated + * light sources. */ + if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING) { + if (!(path_flag & PATH_RAY_CAMERA) && + kernel_data_fetch(objects, sd->object).shadow_set_membership != LIGHT_LINK_MASK_ALL) + { + return; + } + } +#endif + /* Evaluate emissive closure. */ Spectrum L = surface_shader_emission(sd); float mis_weight = 1.0f; @@ -143,9 +156,9 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, ccl_device_inline IntegratorShadowState integrate_direct_light_shadow_init_common(KernelGlobals kg, IntegratorState state, - ccl_private const LightSample *ls, ccl_private const Ray *ccl_restrict ray, const Spectrum bsdf_spectrum, + const int light_group, const int mnee_vertex_count) { @@ -203,10 +216,7 @@ integrate_direct_light_shadow_init_common(KernelGlobals kg, } /* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */ - INTEGRATOR_STATE_WRITE( - shadow_state, shadow_path, lightgroup) = (ls->type != LIGHT_BACKGROUND) ? - ls->group + 1 : - kernel_data.background.lightgroup + 1; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, lightgroup) = light_group; #ifdef __PATH_GUIDING__ INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput; @@ -330,8 +340,13 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, } /* Branch off shadow kernel. */ + + // TODO(: De-duplicate with the shade_Dedicated_light. + // Possibly by ensuring ls->group is always assigned properly. + const int light_group = ls.type != LIGHT_BACKGROUND ? ls.group : + kernel_data.background.lightgroup; IntegratorShadowState shadow_state = integrate_direct_light_shadow_init_common( - kg, state, &ls, &ray, bsdf_eval_sum(&bsdf_eval), mnee_vertex_count); + kg, state, &ray, bsdf_eval_sum(&bsdf_eval), mnee_vertex_count, light_group); if (is_transmission) { #ifdef __VOLUME__ diff --git a/intern/cycles/kernel/light/light.h b/intern/cycles/kernel/light/light.h index d5fda025917..7b3ab72363c 100644 --- a/intern/cycles/kernel/light/light.h +++ b/intern/cycles/kernel/light/light.h @@ -245,6 +245,8 @@ ccl_device_noinline bool light_sample(KernelGlobals kg, /* Intersect ray with individual light. */ +/* Returns the total number of hits (the input num_hits plus the number of the new intersections). + */ template ccl_device_forceinline int lights_intersect_impl(KernelGlobals kg, ccl_private const Ray *ccl_restrict ray, @@ -255,14 +257,13 @@ ccl_device_forceinline int lights_intersect_impl(KernelGlobals kg, const uint32_t path_flag, const uint8_t path_mnee, const int receiver_forward, - ccl_private uint *lcg_state) + ccl_private uint *lcg_state, + int num_hits) { #ifdef __SHADOW_LINKING__ const bool is_indirect_ray = !(path_flag & PATH_RAY_CAMERA); #endif - int num_hits = 0; - for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) { const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp); @@ -405,13 +406,17 @@ ccl_device bool lights_intersect(KernelGlobals kg, path_flag, path_mnee, receiver_forward, - nullptr); + nullptr, + 0); return isect->prim != PRIM_NONE; } /* Lights intersection for the shadow linking. - * Intersects spot, point, area, and distant lights. */ + * Intersects spot, point, area, and distant lights. + * + * Returns the total number of hits (the input num_hits plus the number of the new intersections). + */ ccl_device int lights_intersect_shadow_linked(KernelGlobals kg, ccl_private const Ray *ccl_restrict ray, ccl_private Intersection *ccl_restrict isect, @@ -420,7 +425,8 @@ ccl_device int lights_intersect_shadow_linked(KernelGlobals kg, const int last_type, const uint32_t path_flag, const int receiver_forward, - ccl_private uint *lcg_state) + ccl_private uint *lcg_state, + const int num_hits) { return lights_intersect_impl(kg, ray, @@ -431,7 +437,8 @@ ccl_device int lights_intersect_shadow_linked(KernelGlobals kg, path_flag, PATH_MNEE_NONE, receiver_forward, - lcg_state); + lcg_state, + num_hits); } /* Setup light sample from intersection. */ -- 2.30.2