Cycles: Add support for light groups

Light groups are a type of pass that only contains lighting from a subset of light sources.
They are created in the View layer, and light sources (lamps, objects with emissive materials
and/or the environment) can be assigned to a group.

Currently, each light group ends up generating its own version of the Combined pass.
In the future, additional types of passes (e.g. shadowcatcher) might be getting their own
per-lightgroup versions.

The lightgroup creation and assignment is not Cycles-specific, so Eevee or external render
engines could make use of it in the future.

Note that Lightgroups are identified by their name - therefore, the name of the Lightgroup
in the View Layer and the name that's set in an object's settings must match for it to be
included.
Currently, changing a Lightgroup's name does not update objects - this is planned for the
future, along with other features such as denoising for light groups and viewing them in
preview renders.

Original patch by Alex Fuller (@mistaed), with some polishing by Lukas Stockner (@lukasstockner97).

Differential Revision: https://developer.blender.org/D12871
This commit is contained in:
Lukas Stockner
2022-04-02 00:11:11 +02:00
parent 5387d33e5f
commit ad35453cd1
46 changed files with 792 additions and 80 deletions

View File

@@ -320,12 +320,13 @@ ccl_device_inline void kernel_accum_combined_transparent_pass(KernelGlobals kg,
}
/* Write background or emission to appropriate pass. */
ccl_device_inline void kernel_accum_emission_or_background_pass(KernelGlobals kg,
ConstIntegratorState state,
float3 contribution,
ccl_global float *ccl_restrict
buffer,
const int pass)
ccl_device_inline void kernel_accum_emission_or_background_pass(
KernelGlobals kg,
ConstIntegratorState state,
float3 contribution,
ccl_global float *ccl_restrict buffer,
const int pass,
const int lightgroup = LIGHTGROUP_NONE)
{
if (!(kernel_data.film.light_pass_flag & PASS_ANY)) {
return;
@@ -351,52 +352,59 @@ ccl_device_inline void kernel_accum_emission_or_background_pass(KernelGlobals kg
/* Directly visible, write to emission or background pass. */
pass_offset = pass;
}
else if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
else {
/* Don't write any light passes for shadow catcher, for easier
* compositing back together of the combined pass. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) {
return;
}
if (path_flag & PATH_RAY_SURFACE_PASS) {
/* Indirectly visible through reflection. */
const float3 diffuse_weight = INTEGRATOR_STATE(state, path, pass_diffuse_weight);
const float3 glossy_weight = INTEGRATOR_STATE(state, path, pass_glossy_weight);
/* Glossy */
const int glossy_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_glossy_direct :
kernel_data.film.pass_glossy_indirect);
if (glossy_pass_offset != PASS_UNUSED) {
kernel_write_pass_float3(buffer + glossy_pass_offset, glossy_weight * contribution);
}
/* Transmission */
const int transmission_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_transmission_direct :
kernel_data.film.pass_transmission_indirect);
if (transmission_pass_offset != PASS_UNUSED) {
/* Transmission is what remains if not diffuse and glossy, not stored explicitly to save
* GPU memory. */
const float3 transmission_weight = one_float3() - diffuse_weight - glossy_weight;
kernel_write_pass_float3(buffer + transmission_pass_offset,
transmission_weight * contribution);
}
/* Reconstruct diffuse subset of throughput. */
pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_diffuse_direct :
kernel_data.film.pass_diffuse_indirect;
if (pass_offset != PASS_UNUSED) {
contribution *= diffuse_weight;
}
if (lightgroup != LIGHTGROUP_NONE && kernel_data.film.pass_lightgroup != PASS_UNUSED) {
kernel_write_pass_float3(buffer + kernel_data.film.pass_lightgroup + 3 * lightgroup,
contribution);
}
else if (path_flag & PATH_RAY_VOLUME_PASS) {
/* Indirectly visible through volume. */
pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_volume_direct :
kernel_data.film.pass_volume_indirect;
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
if (path_flag & PATH_RAY_SURFACE_PASS) {
/* Indirectly visible through reflection. */
const float3 diffuse_weight = INTEGRATOR_STATE(state, path, pass_diffuse_weight);
const float3 glossy_weight = INTEGRATOR_STATE(state, path, pass_glossy_weight);
/* Glossy */
const int glossy_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_glossy_direct :
kernel_data.film.pass_glossy_indirect);
if (glossy_pass_offset != PASS_UNUSED) {
kernel_write_pass_float3(buffer + glossy_pass_offset, glossy_weight * contribution);
}
/* Transmission */
const int transmission_pass_offset = ((INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_transmission_direct :
kernel_data.film.pass_transmission_indirect);
if (transmission_pass_offset != PASS_UNUSED) {
/* Transmission is what remains if not diffuse and glossy, not stored explicitly to save
* GPU memory. */
const float3 transmission_weight = one_float3() - diffuse_weight - glossy_weight;
kernel_write_pass_float3(buffer + transmission_pass_offset,
transmission_weight * contribution);
}
/* Reconstruct diffuse subset of throughput. */
pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_diffuse_direct :
kernel_data.film.pass_diffuse_indirect;
if (pass_offset != PASS_UNUSED) {
contribution *= diffuse_weight;
}
}
else if (path_flag & PATH_RAY_VOLUME_PASS) {
/* Indirectly visible through volume. */
pass_offset = (INTEGRATOR_STATE(state, path, bounce) == 1) ?
kernel_data.film.pass_volume_direct :
kernel_data.film.pass_volume_indirect;
}
}
}
@@ -449,6 +457,13 @@ ccl_device_inline void kernel_accum_light(KernelGlobals kg,
return;
}
/* Write lightgroup pass. LIGHTGROUP_NONE is ~0 so decode from unsigned to signed */
const int lightgroup = (int)(INTEGRATOR_STATE(state, shadow_path, lightgroup)) - 1;
if (lightgroup != LIGHTGROUP_NONE && kernel_data.film.pass_lightgroup != PASS_UNUSED) {
kernel_write_pass_float3(buffer + kernel_data.film.pass_lightgroup + 3 * lightgroup,
contribution);
}
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
int pass_offset = PASS_UNUSED;
@@ -566,15 +581,20 @@ ccl_device_inline void kernel_accum_background(KernelGlobals kg,
kernel_accum_combined_transparent_pass(
kg, path_flag, sample, contribution, transparent, buffer);
}
kernel_accum_emission_or_background_pass(
kg, state, contribution, buffer, kernel_data.film.pass_background);
kernel_accum_emission_or_background_pass(kg,
state,
contribution,
buffer,
kernel_data.film.pass_background,
kernel_data.background.lightgroup);
}
/* Write emission to render buffer. */
ccl_device_inline void kernel_accum_emission(KernelGlobals kg,
ConstIntegratorState state,
const float3 L,
ccl_global float *ccl_restrict render_buffer)
ccl_global float *ccl_restrict render_buffer,
const int lightgroup = LIGHTGROUP_NONE)
{
float3 contribution = L;
kernel_accum_clamp(kg, &contribution, INTEGRATOR_STATE(state, path, bounce) - 1);
@@ -585,7 +605,7 @@ ccl_device_inline void kernel_accum_emission(KernelGlobals kg,
kernel_accum_combined_pass(kg, path_flag, sample, contribution, buffer);
kernel_accum_emission_or_background_pass(
kg, state, contribution, buffer, kernel_data.film.pass_emission);
kg, state, contribution, buffer, kernel_data.film.pass_emission, lightgroup);
}
CCL_NAMESPACE_END

View File

@@ -283,6 +283,26 @@ ccl_device_inline float object_pass_id(KernelGlobals kg, int object)
return kernel_tex_fetch(__objects, object).pass_id;
}
/* Lightgroup of lamp */
ccl_device_inline int lamp_lightgroup(KernelGlobals kg, int lamp)
{
if (lamp == LAMP_NONE)
return LIGHTGROUP_NONE;
return kernel_tex_fetch(__lights, lamp).lightgroup;
}
/* Lightgroup of object */
ccl_device_inline int object_lightgroup(KernelGlobals kg, int object)
{
if (object == OBJECT_NONE)
return LIGHTGROUP_NONE;
return kernel_tex_fetch(__objects, object).lightgroup;
}
/* Per lamp random number for shader variation */
ccl_device_inline float lamp_random_number(KernelGlobals kg, int lamp)

View File

@@ -186,7 +186,8 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
/* Write to render buffer. */
const float3 throughput = INTEGRATOR_STATE(state, path, throughput);
kernel_accum_emission(kg, state, throughput * light_eval, render_buffer);
kernel_accum_emission(
kg, state, throughput * light_eval, render_buffer, kernel_data.background.lightgroup);
}
}
}

View File

@@ -78,7 +78,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
/* Write to render buffer. */
const float3 throughput = INTEGRATOR_STATE(state, path, throughput);
kernel_accum_emission(kg, state, throughput * light_eval, render_buffer);
kernel_accum_emission(kg, state, throughput * light_eval, render_buffer, ls.group);
}
ccl_device void integrator_shade_light(KernelGlobals kg,

View File

@@ -87,7 +87,8 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
}
const float3 throughput = INTEGRATOR_STATE(state, path, throughput);
kernel_accum_emission(kg, state, throughput * L, render_buffer);
kernel_accum_emission(
kg, state, throughput * L, render_buffer, object_lightgroup(kg, sd->object));
}
#endif /* __EMISSION__ */
@@ -258,6 +259,12 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* 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;
}
#endif

View File

@@ -653,7 +653,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
/* Write accumulated emission. */
if (!is_zero(accum_emission)) {
kernel_accum_emission(kg, state, accum_emission, render_buffer);
kernel_accum_emission(
kg, state, accum_emission, render_buffer, object_lightgroup(kg, sd->object));
}
# ifdef __DENOISING_FEATURES__
@@ -833,6 +834,12 @@ ccl_device_forceinline void integrate_volume_direct_light(
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* 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_copy_volume_stack_to_shadow(kg, shadow_state, state);
}
# endif

View File

@@ -38,6 +38,8 @@ KERNEL_STRUCT_MEMBER(shadow_path, packed_float3, pass_diffuse_weight, KERNEL_FEA
KERNEL_STRUCT_MEMBER(shadow_path, packed_float3, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)
/* Number of intersections found by ray-tracing. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, num_hits, KERNEL_FEATURE_PATH_TRACING)
/* Light group. */
KERNEL_STRUCT_MEMBER(shadow_path, uint8_t, lightgroup, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_END(shadow_path)
/********************************** Shadow Ray *******************************/

View File

@@ -23,6 +23,7 @@ typedef struct LightSample {
int prim; /* primitive id for triangle/curve lights */
int shader; /* shader id */
int lamp; /* lamp id */
int group; /* lightgroup */
LightType type; /* type of light */
} LightSample;
@@ -52,6 +53,7 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
ls->lamp = lamp;
ls->u = randu;
ls->v = randv;
ls->group = lamp_lightgroup(kg, lamp);
if (in_volume_segment && (type == LIGHT_DISTANT || type == LIGHT_BACKGROUND)) {
/* Distant lights in a volume get a dummy sample, position will not actually
@@ -413,6 +415,7 @@ ccl_device bool light_sample_from_distant_ray(KernelGlobals kg,
ls->P = -ray_D;
ls->Ng = -ray_D;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
@@ -441,6 +444,7 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ls->t = isect->t;
ls->P = ray_P + ray_D * ls->t;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
if (type == LIGHT_SPOT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
@@ -706,6 +710,7 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals kg,
ls->lamp = LAMP_NONE;
ls->shader |= SHADER_USE_MIS;
ls->type = LIGHT_TRIANGLE;
ls->group = object_lightgroup(kg, object);
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));

View File

@@ -46,6 +46,7 @@ CCL_NAMESPACE_BEGIN
#define LAMP_NONE (~0)
#define ID_NONE (0.0f)
#define PASS_UNUSED (~0)
#define LIGHTGROUP_NONE (~0)
#define INTEGRATOR_SHADOW_ISECT_SIZE_CPU 1024U
#define INTEGRATOR_SHADOW_ISECT_SIZE_GPU 4U
@@ -1108,6 +1109,7 @@ typedef struct KernelFilm {
int pass_aov_color;
int pass_aov_value;
int pass_lightgroup;
/* XYZ to rendering color space transform. float4 instead of float3 to
* ensure consistent padding/alignment across devices. */
@@ -1192,8 +1194,10 @@ typedef struct KernelBackground {
int use_mis;
int lightgroup;
/* Padding */
int pad1, pad2, pad3;
int pad1, pad2;
} KernelBackground;
static_assert_align(KernelBackground, 16);
@@ -1372,9 +1376,12 @@ typedef struct KernelObject {
float ao_distance;
int lightgroup;
uint visibility;
int primitive_type;
int pad[2];
int pad1;
} KernelObject;
static_assert_align(KernelObject, 16);
@@ -1427,7 +1434,7 @@ typedef struct KernelLight {
float random;
float strength[3];
int use_caustics;
float pad1;
int lightgroup;
Transform tfm;
Transform itfm;
union {