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:
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 *******************************/
|
||||
|
@@ -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));
|
||||
|
||||
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user