Light Linking: Support shadow linking to emissive meshes #107981
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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. */
|
||||
|
|
Loading…
Reference in New Issue
It's not needed.