Light Linking: Support shadow linking to emissive meshes #107981

Merged
Sergey Sharybin merged 1 commits from Sergey/blender:cycles-light-linking-mesh into cycles-light-linking 2023-05-16 18:39:27 +02:00
5 changed files with 252 additions and 60 deletions

View File

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

View File

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

It's not needed.

It's not needed.
int hit_object = (projection_isect.object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, projection_isect.prim) :
projection_isect.object;

View File

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

View File

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

View File

@ -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<bool is_main_path>
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<false>(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. */