diff options
author | Lukas Stockner <lukas.stockner@freenet.de> | 2022-04-02 01:11:11 +0300 |
---|---|---|
committer | Lukas Stockner <lukas.stockner@freenet.de> | 2022-04-02 07:14:27 +0300 |
commit | ad35453cd19b3db779b0b3a90feac2e93c7a73cf (patch) | |
tree | 3ad7893815bda3e34e18302422ad1f978e828603 /intern | |
parent | 5387d33e5f954c4cecdb7ffd3d1042d8632d6c15 (diff) |
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
Diffstat (limited to 'intern')
31 files changed, 308 insertions, 77 deletions
diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index 48dd26cc622..b7713dc7110 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -228,6 +228,10 @@ def list_render_passes(scene, srl): else: yield (aov.name, "RGB", 'COLOR') + # Light groups. + for lightgroup in srl.lightgroups: + yield ("Combined_%s" % lightgroup.name, "RGB", 'COLOR') + def register_passes(engine, scene, view_layer): for name, channelids, channeltype in list_render_passes(scene, view_layer): diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index c86452e0a18..621a5571f63 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -12,7 +12,7 @@ from bpy.types import Panel from bl_ui.properties_grease_pencil_common import GreasePencilSimplifyPanel from bl_ui.properties_render import draw_hair_settings -from bl_ui.properties_view_layer import ViewLayerCryptomattePanel, ViewLayerAOVPanel +from bl_ui.properties_view_layer import ViewLayerCryptomattePanel, ViewLayerAOVPanel, ViewLayerLightgroupsPanel class CyclesPresetPanel(PresetPanel, Panel): COMPAT_ENGINES = {'CYCLES'} @@ -883,6 +883,12 @@ class CYCLES_RENDER_PT_passes_aov(CyclesButtonsPanel, ViewLayerAOVPanel): bl_parent_id = "CYCLES_RENDER_PT_passes" +class CYCLES_RENDER_PT_passes_lightgroups(CyclesButtonsPanel, ViewLayerLightgroupsPanel): + bl_label = "Light Groups" + bl_context = "view_layer" + bl_parent_id = "CYCLES_RENDER_PT_passes" + + class CYCLES_PT_post_processing(CyclesButtonsPanel, Panel): bl_label = "Post Processing" bl_options = {'DEFAULT_CLOSED'} @@ -1147,6 +1153,23 @@ class CYCLES_OBJECT_PT_shading_caustics(CyclesButtonsPanel, Panel): col.prop(cob, "is_caustics_receiver") +class CYCLES_OBJECT_PT_lightgroup(CyclesButtonsPanel, Panel): + bl_label = "Light Group" + bl_parent_id = "CYCLES_OBJECT_PT_shading" + bl_context = "object" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + ob = context.object + + view_layer = context.view_layer + + col = layout.column(align=True) + col.prop_search(ob, "lightgroup", view_layer, "lightgroups", text="Light Group") + + class CYCLES_OBJECT_PT_visibility(CyclesButtonsPanel, Panel): bl_label = "Visibility" bl_context = "object" @@ -1399,10 +1422,14 @@ class CYCLES_WORLD_PT_surface(CyclesButtonsPanel, Panel): layout.use_property_split = True world = context.world + view_layer = context.view_layer if not panel_node_draw(layout, world, 'OUTPUT_WORLD', 'Surface'): layout.prop(world, "color") + col = layout.column(align=True) + col.prop_search(world, "lightgroup", view_layer, "lightgroups", text="Light Group") + class CYCLES_WORLD_PT_volume(CyclesButtonsPanel, Panel): bl_label = "Volume" @@ -2209,6 +2236,7 @@ classes = ( CYCLES_RENDER_PT_passes_light, CYCLES_RENDER_PT_passes_crypto, CYCLES_RENDER_PT_passes_aov, + CYCLES_RENDER_PT_passes_lightgroups, CYCLES_RENDER_PT_filter, CYCLES_RENDER_PT_override, CYCLES_PT_post_processing, @@ -2220,6 +2248,7 @@ classes = ( CYCLES_OBJECT_PT_shading_shadow_terminator, CYCLES_OBJECT_PT_shading_gi_approximation, CYCLES_OBJECT_PT_shading_caustics, + CYCLES_OBJECT_PT_lightgroup, CYCLES_OBJECT_PT_visibility, CYCLES_OBJECT_PT_visibility_ray_visibility, CYCLES_OBJECT_PT_visibility_culling, diff --git a/intern/cycles/blender/light.cpp b/intern/cycles/blender/light.cpp index 04799443ceb..5359fa13505 100644 --- a/intern/cycles/blender/light.cpp +++ b/intern/cycles/blender/light.cpp @@ -143,6 +143,9 @@ void BlenderSync::sync_light(BL::Object &b_parent, light->set_use_scatter((visibility & PATH_RAY_VOLUME_SCATTER) != 0); light->set_is_shadow_catcher(b_ob_info.real_object.is_shadow_catcher()); + /* lightgroup */ + light->set_lightgroup(ustring(b_ob_info.real_object.lightgroup())); + /* tag */ light->tag_update(scene); } diff --git a/intern/cycles/blender/object.cpp b/intern/cycles/blender/object.cpp index 16a6b3454ed..d8f236e0641 100644 --- a/intern/cycles/blender/object.cpp +++ b/intern/cycles/blender/object.cpp @@ -343,6 +343,9 @@ Object *BlenderSync::sync_object(BL::Depsgraph &b_depsgraph, object->set_random_id(hash_uint2(hash_string(object->name.c_str()), 0)); } + /* lightgroup */ + object->set_lightgroup(ustring(b_ob.lightgroup())); + object->tag_update(scene); } diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 224cbea85f3..d3527567b96 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -1532,6 +1532,8 @@ void BlenderSync::sync_world(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, background->set_use_shader(view_layer.use_background_shader || viewport_parameters.use_custom_shader()); + background->set_lightgroup(ustring(b_world ? b_world.lightgroup() : "")); + background->tag_update(scene); } diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 8af2ee7a435..bd6bfafedeb 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -745,6 +745,20 @@ void BlenderSync::sync_render_passes(BL::RenderLayer &b_rlay, BL::ViewLayer &b_v } } + /* Light Group passes. */ + BL::ViewLayer::lightgroups_iterator b_lightgroup_iter; + for (b_view_layer.lightgroups.begin(b_lightgroup_iter); + b_lightgroup_iter != b_view_layer.lightgroups.end(); + ++b_lightgroup_iter) { + BL::Lightgroup b_lightgroup(*b_lightgroup_iter); + + string name = string_printf("Combined_%s", b_lightgroup.name().c_str()); + + b_engine.add_pass(name.c_str(), 3, "RGB", b_view_layer.name().c_str()); + Pass *pass = pass_add(scene, PASS_COMBINED, name.c_str(), PassMode::NOISY); + pass->set_lightgroup(ustring(b_lightgroup.name())); + } + scene->film->set_pass_alpha_threshold(b_view_layer.pass_alpha_threshold()); } diff --git a/intern/cycles/integrator/pass_accessor.cpp b/intern/cycles/integrator/pass_accessor.cpp index 0be3cf6860b..05318b7545b 100644 --- a/intern/cycles/integrator/pass_accessor.cpp +++ b/intern/cycles/integrator/pass_accessor.cpp @@ -18,7 +18,11 @@ CCL_NAMESPACE_BEGIN */ PassAccessor::PassAccessInfo::PassAccessInfo(const BufferPass &pass) - : type(pass.type), mode(pass.mode), include_albedo(pass.include_albedo), offset(pass.offset) + : type(pass.type), + mode(pass.mode), + include_albedo(pass.include_albedo), + is_lightgroup(!pass.lightgroup.empty()), + offset(pass.offset) { } @@ -127,7 +131,8 @@ bool PassAccessor::get_render_tile_pixels(const RenderBuffers *render_buffers, const PassType type = pass_access_info_.type; const PassMode mode = pass_access_info_.mode; - const PassInfo pass_info = Pass::get_info(type, pass_access_info_.include_albedo); + const PassInfo pass_info = Pass::get_info( + type, pass_access_info_.include_albedo, pass_access_info_.is_lightgroup); int num_written_components = pass_info.num_components; if (pass_info.num_components == 1) { @@ -215,8 +220,8 @@ void PassAccessor::init_kernel_film_convert(KernelFilmConvert *kfilm_convert, const Destination &destination) const { const PassMode mode = pass_access_info_.mode; - const PassInfo &pass_info = Pass::get_info(pass_access_info_.type, - pass_access_info_.include_albedo); + const PassInfo &pass_info = Pass::get_info( + pass_access_info_.type, pass_access_info_.include_albedo, pass_access_info_.is_lightgroup); kfilm_convert->pass_offset = pass_access_info_.offset; kfilm_convert->pass_stride = buffer_params.pass_stride; @@ -279,8 +284,8 @@ bool PassAccessor::set_render_tile_pixels(RenderBuffers *render_buffers, const S return false; } - const PassInfo pass_info = Pass::get_info(pass_access_info_.type, - pass_access_info_.include_albedo); + const PassInfo pass_info = Pass::get_info( + pass_access_info_.type, pass_access_info_.include_albedo, pass_access_info_.is_lightgroup); const BufferParams &buffer_params = render_buffers->params; diff --git a/intern/cycles/integrator/pass_accessor.h b/intern/cycles/integrator/pass_accessor.h index 7de1d03961b..683d3a35272 100644 --- a/intern/cycles/integrator/pass_accessor.h +++ b/intern/cycles/integrator/pass_accessor.h @@ -28,6 +28,7 @@ class PassAccessor { PassType type = PASS_NONE; PassMode mode = PassMode::NOISY; bool include_albedo = false; + bool is_lightgroup = false; int offset = -1; /* For the shadow catcher matte pass: whether to approximate shadow catcher pass into its diff --git a/intern/cycles/kernel/film/accumulate.h b/intern/cycles/kernel/film/accumulate.h index d6a385a4bff..4c4165f3640 100644 --- a/intern/cycles/kernel/film/accumulate.h +++ b/intern/cycles/kernel/film/accumulate.h @@ -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); - } + if (lightgroup != LIGHTGROUP_NONE && kernel_data.film.pass_lightgroup != PASS_UNUSED) { + kernel_write_pass_float3(buffer + kernel_data.film.pass_lightgroup + 3 * lightgroup, + 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); - } + 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); - /* 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; + /* 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; } - } - 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 diff --git a/intern/cycles/kernel/geom/object.h b/intern/cycles/kernel/geom/object.h index 86c57c84b47..3faab7fa905 100644 --- a/intern/cycles/kernel/geom/object.h +++ b/intern/cycles/kernel/geom/object.h @@ -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) diff --git a/intern/cycles/kernel/integrator/shade_background.h b/intern/cycles/kernel/integrator/shade_background.h index 4fd41121466..62b3ce1c15c 100644 --- a/intern/cycles/kernel/integrator/shade_background.h +++ b/intern/cycles/kernel/integrator/shade_background.h @@ -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); } } } diff --git a/intern/cycles/kernel/integrator/shade_light.h b/intern/cycles/kernel/integrator/shade_light.h index 4a519a76ed0..be926c78439 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -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, diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index c5dd9fe27f4..a9bf3b5b432 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -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 diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index acda4b8e9d1..4a5015946aa 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -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 diff --git a/intern/cycles/kernel/integrator/shadow_state_template.h b/intern/cycles/kernel/integrator/shadow_state_template.h index 9308df53e46..eaee65ada40 100644 --- a/intern/cycles/kernel/integrator/shadow_state_template.h +++ b/intern/cycles/kernel/integrator/shadow_state_template.h @@ -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 *******************************/ diff --git a/intern/cycles/kernel/light/light.h b/intern/cycles/kernel/light/light.h index fb637008ca4..1df1615ed99 100644 --- a/intern/cycles/kernel/light/light.h +++ b/intern/cycles/kernel/light/light.h @@ -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)); diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 00b5b007e11..9d9daaa0dda 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -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 { diff --git a/intern/cycles/scene/background.cpp b/intern/cycles/scene/background.cpp index 1c3a9f9358d..bffc8895bfd 100644 --- a/intern/cycles/scene/background.cpp +++ b/intern/cycles/scene/background.cpp @@ -32,6 +32,8 @@ NODE_DEFINE(Background) SOCKET_NODE(shader, "Shader", Shader::get_node_type()); + SOCKET_STRING(lightgroup, "Light Group", ustring()); + return type; } @@ -101,6 +103,15 @@ void Background::device_update(Device *device, DeviceScene *dscene, Scene *scene kbackground->surface_shader |= SHADER_EXCLUDE_CAMERA; } + /* Light group. */ + auto it = scene->lightgroups.find(lightgroup); + if (it != scene->lightgroups.end()) { + kbackground->lightgroup = it->second; + } + else { + kbackground->lightgroup = LIGHTGROUP_NONE; + } + clear_modified(); } diff --git a/intern/cycles/scene/background.h b/intern/cycles/scene/background.h index bbffce6e30a..50b4368d708 100644 --- a/intern/cycles/scene/background.h +++ b/intern/cycles/scene/background.h @@ -30,6 +30,8 @@ class Background : public Node { NODE_SOCKET_API(float, volume_step_size) + NODE_SOCKET_API(ustring, lightgroup) + Background(); ~Background(); diff --git a/intern/cycles/scene/film.cpp b/intern/cycles/scene/film.cpp index e17c839d60b..c3b126544c4 100644 --- a/intern/cycles/scene/film.cpp +++ b/intern/cycles/scene/film.cpp @@ -175,6 +175,7 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) kfilm->pass_volume_direct = PASS_UNUSED; kfilm->pass_volume_indirect = PASS_UNUSED; kfilm->pass_shadow = PASS_UNUSED; + kfilm->pass_lightgroup = PASS_UNUSED; /* Mark passes as unused so that the kernel knows the pass is inaccessible. */ kfilm->pass_denoising_normal = PASS_UNUSED; @@ -189,6 +190,7 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) bool have_cryptomatte = false; bool have_aov_color = false; bool have_aov_value = false; + bool have_lightgroup = false; for (size_t i = 0; i < scene->passes.size(); i++) { const Pass *pass = scene->passes[i]; @@ -223,6 +225,15 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) assert(pass->get_type() <= PASS_CATEGORY_BAKE_END); } + if (pass->get_lightgroup() != ustring()) { + if (!have_lightgroup) { + kfilm->pass_lightgroup = kfilm->pass_stride; + have_lightgroup = true; + } + kfilm->pass_stride += pass->get_info().num_components; + continue; + } + switch (pass->get_type()) { case PASS_COMBINED: kfilm->pass_combined = kfilm->pass_stride; @@ -414,6 +425,26 @@ int Film::get_aov_offset(Scene *scene, string name, bool &is_color) return -1; } +bool Film::update_lightgroups(Scene *scene) +{ + map<ustring, int> lightgroups; + int i = 0; + foreach (const Pass *pass, scene->passes) { + ustring lightgroup = pass->get_lightgroup(); + if (!lightgroup.empty()) { + if (!lightgroups.count(lightgroup)) { + lightgroups[lightgroup] = i++; + } + } + } + if (scene->lightgroups != lightgroups) { + scene->lightgroups = lightgroups; + return true; + } + + return false; +} + void Film::update_passes(Scene *scene, bool add_sample_count_pass) { const Background *background = scene->background; @@ -580,11 +611,19 @@ void Film::remove_auto_passes(Scene *scene) static bool compare_pass_order(const Pass *a, const Pass *b) { + /* On the highest level, sort by number of components. + * Within passes of the same component count, sort so that all non-lightgroup passes come first. + * Within that group, sort by type. */ const int num_components_a = a->get_info().num_components; const int num_components_b = b->get_info().num_components; if (num_components_a == num_components_b) { - return (a->get_type() < b->get_type()); + const int is_lightgroup_a = !a->get_lightgroup().empty(); + const int is_lightgroup_b = !b->get_lightgroup().empty(); + if (is_lightgroup_a == is_lightgroup_b) { + return (a->get_type() < b->get_type()); + } + return is_lightgroup_b; } return num_components_a > num_components_b; diff --git a/intern/cycles/scene/film.h b/intern/cycles/scene/film.h index 43597b0fc79..f1e3237eb9e 100644 --- a/intern/cycles/scene/film.h +++ b/intern/cycles/scene/film.h @@ -68,6 +68,8 @@ class Film : public Node { int get_aov_offset(Scene *scene, string name, bool &is_color); + bool update_lightgroups(Scene *scene); + /* Update passes so that they contain all passes required for the configured functionality. * * If `add_sample_count_pass` is true then the SAMPLE_COUNT pass is ensured to be added. */ diff --git a/intern/cycles/scene/light.cpp b/intern/cycles/scene/light.cpp index 85a7f37994c..5e311d3051f 100644 --- a/intern/cycles/scene/light.cpp +++ b/intern/cycles/scene/light.cpp @@ -134,6 +134,8 @@ NODE_DEFINE(Light) SOCKET_NODE(shader, "Shader", Shader::get_node_type()); + SOCKET_STRING(lightgroup, "Light Group", ustring()); + return type; } @@ -902,6 +904,14 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc klights[light_index].tfm = light->tfm; klights[light_index].itfm = transform_inverse(light->tfm); + auto it = scene->lightgroups.find(light->lightgroup); + if (it != scene->lightgroups.end()) { + klights[light_index].lightgroup = it->second; + } + else { + klights[light_index].lightgroup = LIGHTGROUP_NONE; + } + light_index++; } diff --git a/intern/cycles/scene/light.h b/intern/cycles/scene/light.h index 25190f6fb63..5b852f210fc 100644 --- a/intern/cycles/scene/light.h +++ b/intern/cycles/scene/light.h @@ -71,6 +71,8 @@ class Light : public Node { NODE_SOCKET_API(int, max_bounces) NODE_SOCKET_API(uint, random_id) + NODE_SOCKET_API(ustring, lightgroup) + void tag_update(Scene *scene); /* Check whether the light has contribution the scene. */ diff --git a/intern/cycles/scene/object.cpp b/intern/cycles/scene/object.cpp index 9b19169880a..55d89fc3673 100644 --- a/intern/cycles/scene/object.cpp +++ b/intern/cycles/scene/object.cpp @@ -98,6 +98,8 @@ NODE_DEFINE(Object) SOCKET_FLOAT(ao_distance, "AO Distance", 0.0f); + SOCKET_STRING(lightgroup, "Light Group", ustring()); + return type; } @@ -393,7 +395,8 @@ static float object_volume_density(const Transform &tfm, Geometry *geom) void ObjectManager::device_update_object_transform(UpdateObjectTransformState *state, Object *ob, - bool update_all) + bool update_all, + const Scene *scene) { KernelObject &kobject = state->objects[ob->index]; Transform *object_motion_pass = state->object_motion_pass; @@ -532,6 +535,15 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s if (geom->geometry_type == Geometry::HAIR) { state->have_curves = true; } + + /* Light group. */ + auto it = scene->lightgroups.find(ob->lightgroup); + if (it != scene->lightgroups.end()) { + kobject.lightgroup = it->second; + } + else { + kobject.lightgroup = LIGHTGROUP_NONE; + } } void ObjectManager::device_update_prim_offsets(Device *device, DeviceScene *dscene, Scene *scene) @@ -618,7 +630,7 @@ void ObjectManager::device_update_transforms(DeviceScene *dscene, Scene *scene, [&](const blocked_range<size_t> &r) { for (size_t i = r.begin(); i != r.end(); i++) { Object *ob = state.scene->objects[i]; - device_update_object_transform(&state, ob, update_all); + device_update_object_transform(&state, ob, update_all, scene); } }); diff --git a/intern/cycles/scene/object.h b/intern/cycles/scene/object.h index 561ecd4e7e9..c41f1416180 100644 --- a/intern/cycles/scene/object.h +++ b/intern/cycles/scene/object.h @@ -66,6 +66,8 @@ class Object : public Node { NODE_SOCKET_API(float, ao_distance) + NODE_SOCKET_API(ustring, lightgroup) + /* Set during device update. */ bool intersects_volume; @@ -169,7 +171,8 @@ class ObjectManager { protected: void device_update_object_transform(UpdateObjectTransformState *state, Object *ob, - bool update_all); + bool update_all, + const Scene *scene); void device_update_object_transform_task(UpdateObjectTransformState *state); bool device_update_object_transform_pop_work(UpdateObjectTransformState *state, int *start_index, diff --git a/intern/cycles/scene/pass.cpp b/intern/cycles/scene/pass.cpp index 41730cb189d..5f5b19e710d 100644 --- a/intern/cycles/scene/pass.cpp +++ b/intern/cycles/scene/pass.cpp @@ -124,6 +124,7 @@ NODE_DEFINE(Pass) SOCKET_ENUM(mode, "Mode", *pass_mode_enum, static_cast<int>(PassMode::DENOISED)); SOCKET_STRING(name, "Name", ustring()); SOCKET_BOOLEAN(include_albedo, "Include Albedo", false); + SOCKET_STRING(lightgroup, "Light Group", ustring()); return type; } @@ -134,7 +135,7 @@ Pass::Pass() : Node(get_node_type()), is_auto_(false) PassInfo Pass::get_info() const { - return get_info(type, include_albedo); + return get_info(type, include_albedo, !lightgroup.empty()); } bool Pass::is_written() const @@ -142,7 +143,7 @@ bool Pass::is_written() const return get_info().is_written; } -PassInfo Pass::get_info(const PassType type, const bool include_albedo) +PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bool is_lightgroup) { PassInfo pass_info; @@ -157,9 +158,9 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo) pass_info.num_components = 0; break; case PASS_COMBINED: - pass_info.num_components = 4; + pass_info.num_components = is_lightgroup ? 3 : 4; pass_info.use_exposure = true; - pass_info.support_denoise = true; + pass_info.support_denoise = !is_lightgroup; break; case PASS_DEPTH: pass_info.num_components = 1; @@ -369,13 +370,16 @@ const Pass *Pass::find(const vector<Pass *> &passes, const string &name) return nullptr; } -const Pass *Pass::find(const vector<Pass *> &passes, PassType type, PassMode mode) +const Pass *Pass::find(const vector<Pass *> &passes, + PassType type, + PassMode mode, + const ustring &lightgroup) { for (const Pass *pass : passes) { - if (pass->get_type() != type || pass->get_mode() != mode) { + if (pass->get_type() != type || pass->get_mode() != mode || + pass->get_lightgroup() != lightgroup) { continue; } - return pass; } diff --git a/intern/cycles/scene/pass.h b/intern/cycles/scene/pass.h index c12df007b5d..e0689eba688 100644 --- a/intern/cycles/scene/pass.h +++ b/intern/cycles/scene/pass.h @@ -53,6 +53,7 @@ class Pass : public Node { NODE_SOCKET_API(PassMode, mode) NODE_SOCKET_API(ustring, name) NODE_SOCKET_API(bool, include_albedo) + NODE_SOCKET_API(ustring, lightgroup) Pass(); @@ -72,7 +73,9 @@ class Pass : public Node { static const NodeEnum *get_type_enum(); static const NodeEnum *get_mode_enum(); - static PassInfo get_info(PassType type, const bool include_albedo = false); + static PassInfo get_info(PassType type, + const bool include_albedo = false, + const bool is_lightgroup = false); static bool contains(const vector<Pass *> &passes, PassType type); @@ -80,7 +83,8 @@ class Pass : public Node { static const Pass *find(const vector<Pass *> &passes, const string &name); static const Pass *find(const vector<Pass *> &passes, PassType type, - PassMode mode = PassMode::NOISY); + PassMode mode = PassMode::NOISY, + const ustring &lightgroup = ustring()); /* Returns PASS_UNUSED if there is no corresponding pass. */ static int get_offset(const vector<Pass *> &passes, const Pass *pass); diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index 359e2d8e2c3..b6b53004816 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -251,6 +251,11 @@ void Scene::device_update(Device *device_, Progress &progress) * - Lookup tables are done a second time to handle film tables */ + if (film->update_lightgroups(this)) { + light_manager->tag_update(this, ccl::LightManager::LIGHT_MODIFIED); + object_manager->tag_update(this, ccl::ObjectManager::OBJECT_MODIFIED); + } + progress.set_status("Updating Shaders"); shader_manager->device_update(device, &dscene, this, progress); diff --git a/intern/cycles/scene/scene.h b/intern/cycles/scene/scene.h index e1b36899302..9e3e8d61b80 100644 --- a/intern/cycles/scene/scene.h +++ b/intern/cycles/scene/scene.h @@ -197,6 +197,9 @@ class Scene : public NodeOwner { /* Optional name. Is used for logging and reporting. */ string name; + /* Maps from Light group names to their pass ID. */ + map<ustring, int> lightgroups; + /* data */ BVH *bvh; Camera *camera; diff --git a/intern/cycles/session/buffers.cpp b/intern/cycles/session/buffers.cpp index f9893ed1e1c..3bbc205ca7a 100644 --- a/intern/cycles/session/buffers.cpp +++ b/intern/cycles/session/buffers.cpp @@ -49,6 +49,7 @@ NODE_DEFINE(BufferPass) SOCKET_ENUM(mode, "Mode", *pass_mode_enum, static_cast<int>(PassMode::DENOISED)); SOCKET_STRING(name, "Name", ustring()); SOCKET_BOOLEAN(include_albedo, "Include Albedo", false); + SOCKET_STRING(lightgroup, "Light Group", ustring()); SOCKET_INT(offset, "Offset", -1); @@ -64,13 +65,14 @@ BufferPass::BufferPass(const Pass *scene_pass) type(scene_pass->get_type()), mode(scene_pass->get_mode()), name(scene_pass->get_name()), - include_albedo(scene_pass->get_include_albedo()) + include_albedo(scene_pass->get_include_albedo()), + lightgroup(scene_pass->get_lightgroup()) { } PassInfo BufferPass::get_info() const { - return Pass::get_info(type, include_albedo); + return Pass::get_info(type, include_albedo, !lightgroup.empty()); } /* -------------------------------------------------------------------- diff --git a/intern/cycles/session/buffers.h b/intern/cycles/session/buffers.h index 1c4ec81e427..a8278388a28 100644 --- a/intern/cycles/session/buffers.h +++ b/intern/cycles/session/buffers.h @@ -30,6 +30,7 @@ class BufferPass : public Node { PassMode mode = PassMode::NOISY; ustring name; bool include_albedo = false; + ustring lightgroup; int offset = -1; @@ -49,7 +50,8 @@ class BufferPass : public Node { inline bool operator==(const BufferPass &other) const { return type == other.type && mode == other.mode && name == other.name && - include_albedo == other.include_albedo && offset == other.offset; + include_albedo == other.include_albedo && lightgroup == other.lightgroup && + offset == other.offset; } inline bool operator!=(const BufferPass &other) const { |