From 75a6d3abf75f3082adf5240ae34973844c0d9a09 Mon Sep 17 00:00:00 2001 From: Sebastian Herhoz Date: Wed, 21 Sep 2022 17:58:34 +0200 Subject: [PATCH] Cycles: add Path Guiding on CPU through Intel OpenPGL This adds path guiding features into Cycles by integrating Intel's Open Path Guiding Library. It can be enabled in the Sampling > Path Guiding panel in the render properties. This feature helps reduce noise in scenes where finding a path to light is difficult for regular path tracing. The current implementation supports guiding directional sampling decisions on surfaces, when the material contains a least one diffuse component, and in volumes with isotropic and anisotropic Henyey-Greenstein phase functions. On surfaces, the guided sampling decision is proportional to the product of the incident radiance and the normal-oriented cosine lobe and in volumes it is proportional to the product of the incident radiance and the phase function. The incident radiance field of a scene is learned and updated during rendering after each per-frame rendering iteration/progression. At the moment, path guiding is only supported by the CPU backend. Support for GPU backends will be added in future versions of OpenPGL. Ref T92571 Differential Revision: https://developer.blender.org/D15286 --- CMakeLists.txt | 1 + build_files/cmake/config/blender_full.cmake | 1 + .../cmake/config/blender_release.cmake | 1 + intern/cycles/CMakeLists.txt | 18 + intern/cycles/blender/addon/engine.py | 5 + intern/cycles/blender/addon/properties.py | 82 ++- intern/cycles/blender/addon/ui.py | 59 ++ intern/cycles/blender/python.cpp | 10 + intern/cycles/blender/sync.cpp | 27 + intern/cycles/cmake/external_libs.cmake | 23 + intern/cycles/cmake/macros.cmake | 3 + intern/cycles/device/cpu/device.cpp | 7 + intern/cycles/device/cpu/device_impl.cpp | 18 + intern/cycles/device/cpu/device_impl.h | 8 + .../device/cpu/kernel_thread_globals.cpp | 27 +- .../cycles/device/cpu/kernel_thread_globals.h | 2 +- intern/cycles/device/device.cpp | 2 + intern/cycles/device/device.h | 11 + intern/cycles/integrator/CMakeLists.txt | 6 + intern/cycles/integrator/guiding.h | 32 ++ intern/cycles/integrator/path_trace.cpp | 129 +++++ intern/cycles/integrator/path_trace.h | 33 ++ intern/cycles/integrator/path_trace_work.h | 7 + .../cycles/integrator/path_trace_work_cpu.cpp | 110 ++++ .../cycles/integrator/path_trace_work_cpu.h | 17 + intern/cycles/integrator/render_scheduler.cpp | 15 +- intern/cycles/integrator/render_scheduler.h | 6 + intern/cycles/kernel/CMakeLists.txt | 1 + intern/cycles/kernel/data_template.h | 17 +- intern/cycles/kernel/device/cpu/globals.h | 19 +- intern/cycles/kernel/integrator/guiding.h | 542 ++++++++++++++++++ .../kernel/integrator/intersect_closest.h | 11 +- intern/cycles/kernel/integrator/mnee.h | 2 +- intern/cycles/kernel/integrator/path_state.h | 11 +- .../kernel/integrator/shade_background.h | 3 + intern/cycles/kernel/integrator/shade_light.h | 3 + .../cycles/kernel/integrator/shade_shadow.h | 2 + .../cycles/kernel/integrator/shade_surface.h | 102 +++- .../cycles/kernel/integrator/shade_volume.h | 125 +++- .../kernel/integrator/shadow_state_template.h | 10 + intern/cycles/kernel/integrator/state.h | 4 + .../cycles/kernel/integrator/state_template.h | 33 ++ intern/cycles/kernel/integrator/subsurface.h | 3 + .../kernel/integrator/subsurface_disk.h | 9 +- .../integrator/subsurface_random_walk.h | 16 +- .../cycles/kernel/integrator/surface_shader.h | 281 ++++++++- .../cycles/kernel/integrator/volume_shader.h | 277 +++++++-- intern/cycles/kernel/types.h | 26 + intern/cycles/scene/film.cpp | 13 + intern/cycles/scene/integrator.cpp | 46 ++ intern/cycles/scene/integrator.h | 13 + intern/cycles/scene/pass.cpp | 15 + intern/cycles/scene/scene.cpp | 8 +- intern/cycles/scene/scene.h | 2 +- intern/cycles/session/session.cpp | 7 + intern/cycles/util/CMakeLists.txt | 1 + intern/cycles/util/guiding.h | 41 ++ tests/python/CMakeLists.txt | 5 + tests/python/cycles_render_tests.py | 2 + 59 files changed, 2185 insertions(+), 125 deletions(-) create mode 100644 intern/cycles/integrator/guiding.h create mode 100644 intern/cycles/kernel/integrator/guiding.h create mode 100644 intern/cycles/util/guiding.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ef3309ded48..d8adf6c396f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -429,6 +429,7 @@ mark_as_advanced(WITH_CPU_SIMD) # Cycles option(WITH_CYCLES "Enable Cycles Render Engine" ON) option(WITH_CYCLES_OSL "Build Cycles with OpenShadingLanguage support" ON) +option(WITH_CYCLES_PATH_GUIDING "Build Cycles with path guiding support" ON) option(WITH_CYCLES_EMBREE "Build Cycles with Embree support" ON) option(WITH_CYCLES_LOGGING "Build Cycles with logging support" ON) option(WITH_CYCLES_DEBUG "Build Cycles with options useful for debugging (e.g., MIS)" OFF) diff --git a/build_files/cmake/config/blender_full.cmake b/build_files/cmake/config/blender_full.cmake index 27577a9fbf7..958eb17522b 100644 --- a/build_files/cmake/config/blender_full.cmake +++ b/build_files/cmake/config/blender_full.cmake @@ -17,6 +17,7 @@ set(WITH_COMPOSITOR_CPU ON CACHE BOOL "" FORCE) set(WITH_CYCLES ON CACHE BOOL "" FORCE) set(WITH_CYCLES_EMBREE ON CACHE BOOL "" FORCE) set(WITH_CYCLES_OSL ON CACHE BOOL "" FORCE) +set(WITH_CYCLES_PATH_GUIDING ON CACHE BOOL "" FORCE) set(WITH_DRACO ON CACHE BOOL "" FORCE) set(WITH_FFTW3 ON CACHE BOOL "" FORCE) set(WITH_FREESTYLE ON CACHE BOOL "" FORCE) diff --git a/build_files/cmake/config/blender_release.cmake b/build_files/cmake/config/blender_release.cmake index 793d1cb0853..d5f5230862b 100644 --- a/build_files/cmake/config/blender_release.cmake +++ b/build_files/cmake/config/blender_release.cmake @@ -18,6 +18,7 @@ set(WITH_COMPOSITOR_CPU ON CACHE BOOL "" FORCE) set(WITH_CYCLES ON CACHE BOOL "" FORCE) set(WITH_CYCLES_EMBREE ON CACHE BOOL "" FORCE) set(WITH_CYCLES_OSL ON CACHE BOOL "" FORCE) +set(WITH_CYCLES_PATH_GUIDING ON CACHE BOOL "" FORCE) set(WITH_DRACO ON CACHE BOOL "" FORCE) set(WITH_FFTW3 ON CACHE BOOL "" FORCE) set(WITH_FREESTYLE ON CACHE BOOL "" FORCE) diff --git a/intern/cycles/CMakeLists.txt b/intern/cycles/CMakeLists.txt index 8adb032ad9e..f619e6b104e 100644 --- a/intern/cycles/CMakeLists.txt +++ b/intern/cycles/CMakeLists.txt @@ -347,6 +347,24 @@ if(WITH_OPENCOLORIO) ) endif() +if(WITH_CYCLES_PATH_GUIDING) + add_definitions(-DWITH_PATH_GUIDING) + + # The level of the guiding integration. + # Different levels can be selected to measure the overhead of different stages. + # 1 = recording the path segments + # 2 = 1 + generating (not storing) sample data from the segments + # 3 = 2 + storing the generates sample data + # 4 = 3 + training the guiding fields + # 5 = 4 + querying the trained guiding for sampling (full path guiding) + add_definitions(-DPATH_GUIDING_LEVEL=5) + + include_directories( + SYSTEM + ${OPENPGL_INCLUDE_DIR} + ) +endif() + # NaN debugging if(WITH_CYCLES_DEBUG_NAN) add_definitions(-DWITH_CYCLES_DEBUG_NAN) diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index 1a99674d239..794338fe78e 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -156,6 +156,11 @@ def with_osl(): return _cycles.with_osl +def with_path_guiding(): + import _cycles + return _cycles.with_path_guiding + + def system_info(): import _cycles return _cycles.system_info() diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index cd263e5b9c4..b5ac39d09e6 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -179,6 +179,12 @@ enum_view3d_shading_render_pass = ( ('SAMPLE_COUNT', "Sample Count", "Per-pixel number of samples"), ) +enum_guiding_distribution = ( + ('PARALLAX_AWARE_VMM', "Parallax-Aware VMM", "Use Parallax-aware von Mises-Fisher models as directional distribution", 0), + ('DIRECTIONAL_QUAD_TREE', "Directional Quad Tree", "Use Directional Quad Trees as directional distribution", 1), + ('VMM', "VMM", "Use von Mises-Fisher models as directional distribution", 2), +) + def enum_openimagedenoise_denoiser(self, context): import _cycles @@ -358,7 +364,9 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): preview_samples: IntProperty( name="Viewport Samples", description="Number of samples to render in the viewport, unlimited if 0", - min=0, max=(1 << 24), + min=0, + soft_min=1, + max=(1 << 24), default=1024, ) @@ -507,6 +515,78 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): default=1.0, ) + use_guiding: BoolProperty( + name="Guiding", + description="Use path guiding for sampling paths. Path guiding incrementally " + "learns the light distribution of the scene and guides path into directions " + "with high direct and indirect light contributions", + default=False, + ) + + use_deterministic_guiding: BoolProperty( + name="Deterministic", + description="Makes path guiding deterministic which means renderings will be" + "reproducible with the same pixel values every time. This feature slows down" + "training", + default=True, + ) + + guiding_distribution_type: EnumProperty( + name="Guiding Distribution Type", + description="Type of representation for the guiding distribution", + items=enum_guiding_distribution, + default='PARALLAX_AWARE_VMM', + ) + + use_surface_guiding: BoolProperty( + name="Surface Guiding", + description="Use guiding when sampling directions on a surface", + default=True, + ) + + surface_guiding_probability: FloatProperty( + name="Surface Guiding Probability", + description="The probability of guiding a direction on a surface", + min=0.0, max=1.0, + default=0.5, + ) + + use_volume_guiding: BoolProperty( + name="Volume Guiding", + description="Use guiding when sampling directions inside a volume", + default=True, + ) + + guiding_training_samples: IntProperty( + name="Training Samples", + description="The maximum number of samples used for training path guiding. " + "Higher samples lead to more accurate guiding, however may also unnecessarily slow " + "down rendering once guiding is accurate enough. " + "A value 0 will continue training until the last sample", + min=0, + soft_min=1, + default=128, + ) + + volume_guiding_probability: FloatProperty( + name="Volume Guiding Probability", + description="The probability of guiding a direction inside a volume", + min=0.0, max=1.0, + default=0.5, + ) + + use_guiding_direct_light: BoolProperty( + name="Guide Direct Light", + description="Consider the contribution of directly visible light sources during guiding", + default=True, + ) + + use_guiding_mis_weights: BoolProperty( + name="Use MIS Weights", + description="Use the MIS weight to weight the contribution of directly visible light sources during guiding", + default=True, + ) + max_bounces: IntProperty( name="Max Bounces", description="Total maximum number of bounces", diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index ee284dd899a..7036e58f6fb 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -278,6 +278,63 @@ class CYCLES_RENDER_PT_sampling_render_denoise(CyclesButtonsPanel, Panel): col.prop(cscene, "denoising_prefilter", text="Prefilter") +class CYCLES_RENDER_PT_sampling_path_guiding(CyclesButtonsPanel, Panel): + bl_label = "Path Guiding" + bl_parent_id = "CYCLES_RENDER_PT_sampling" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + from . import engine + return use_cpu(context) and engine.with_path_guiding() + + def draw_header(self, context): + scene = context.scene + cscene = scene.cycles + + self.layout.prop(cscene, "use_guiding", text="") + + def draw(self, context): + scene = context.scene + cscene = scene.cycles + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.active = cscene.use_guiding + + col = layout.column(align=True) + col.prop(cscene, "use_surface_guiding") + col.prop(cscene, "use_volume_guiding") + col.prop(cscene, "guiding_training_samples") + + +class CYCLES_RENDER_PT_sampling_path_guiding_debug(CyclesDebugButtonsPanel, Panel): + bl_label = "Debug" + bl_parent_id = "CYCLES_RENDER_PT_sampling_path_guiding" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + scene = context.scene + cscene = scene.cycles + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.active = cscene.use_guiding + + layout.prop(cscene, "guiding_distribution_type", text="Distribution Type") + + col = layout.column(align=True) + col.prop(cscene, "surface_guiding_probability") + col.prop(cscene, "volume_guiding_probability") + + col = layout.column(align=True) + col.prop(cscene, "use_deterministic_guiding") + col.prop(cscene, "use_guiding_direct_light") + col.prop(cscene, "use_guiding_mis_weights") + + class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel): bl_label = "Advanced" bl_parent_id = "CYCLES_RENDER_PT_sampling" @@ -2286,6 +2343,8 @@ classes = ( CYCLES_RENDER_PT_sampling_viewport_denoise, CYCLES_RENDER_PT_sampling_render, CYCLES_RENDER_PT_sampling_render_denoise, + CYCLES_RENDER_PT_sampling_path_guiding, + CYCLES_RENDER_PT_sampling_path_guiding_debug, CYCLES_RENDER_PT_sampling_advanced, CYCLES_RENDER_PT_light_paths, CYCLES_RENDER_PT_light_paths_max_bounces, diff --git a/intern/cycles/blender/python.cpp b/intern/cycles/blender/python.cpp index 077875aecb2..9e42f6b8b60 100644 --- a/intern/cycles/blender/python.cpp +++ b/intern/cycles/blender/python.cpp @@ -15,6 +15,7 @@ #include "util/debug.h" #include "util/foreach.h" +#include "util/guiding.h" #include "util/log.h" #include "util/md5.h" #include "util/opengl.h" @@ -1008,6 +1009,15 @@ void *CCL_python_module_init() PyModule_AddStringConstant(mod, "osl_version_string", "unknown"); #endif + if (ccl::guiding_supported()) { + PyModule_AddObject(mod, "with_path_guiding", Py_True); + Py_INCREF(Py_True); + } + else { + PyModule_AddObject(mod, "with_path_guiding", Py_False); + Py_INCREF(Py_False); + } + #ifdef WITH_EMBREE PyModule_AddObject(mod, "with_embree", Py_True); Py_INCREF(Py_True); diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 6081c4626f0..23bc92de022 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -413,6 +413,22 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background) integrator->set_direct_light_sampling_type(direct_light_sampling_type); #endif + integrator->set_use_guiding(get_boolean(cscene, "use_guiding")); + integrator->set_use_surface_guiding(get_boolean(cscene, "use_surface_guiding")); + integrator->set_use_volume_guiding(get_boolean(cscene, "use_volume_guiding")); + integrator->set_guiding_training_samples(get_int(cscene, "guiding_training_samples")); + + if (use_developer_ui) { + integrator->set_deterministic_guiding(get_boolean(cscene, "use_deterministic_guiding")); + integrator->set_surface_guiding_probability(get_float(cscene, "surface_guiding_probability")); + integrator->set_volume_guiding_probability(get_float(cscene, "volume_guiding_probability")); + integrator->set_use_guiding_direct_light(get_boolean(cscene, "use_guiding_direct_light")); + integrator->set_use_guiding_mis_weights(get_boolean(cscene, "use_guiding_mis_weights")); + GuidingDistributionType guiding_distribution_type = (GuidingDistributionType)get_enum( + cscene, "guiding_distribution_type", GUIDING_NUM_TYPES, GUIDING_TYPE_PARALLAX_AWARE_VMM); + integrator->set_guiding_distribution_type(guiding_distribution_type); + } + DenoiseParams denoise_params = get_denoise_params(b_scene, b_view_layer, background); /* No denoising support for vertex color baking, vertices packed into image @@ -737,6 +753,17 @@ void BlenderSync::sync_render_passes(BL::RenderLayer &b_rlay, BL::ViewLayer &b_v pass_add(scene, PASS_DENOISING_DEPTH, "Denoising Depth", PassMode::NOISY); } +#ifdef WITH_CYCLES_DEBUG + b_engine.add_pass("Guiding Color", 3, "RGB", b_view_layer.name().c_str()); + pass_add(scene, PASS_GUIDING_COLOR, "Guiding Color", PassMode::NOISY); + + b_engine.add_pass("Guiding Probability", 1, "X", b_view_layer.name().c_str()); + pass_add(scene, PASS_GUIDING_PROBABILITY, "Guiding Probability", PassMode::NOISY); + + b_engine.add_pass("Guiding Average Roughness", 1, "X", b_view_layer.name().c_str()); + pass_add(scene, PASS_GUIDING_AVG_ROUGHNESS, "Guiding Average Roughness", PassMode::NOISY); +#endif + /* Custom AOV passes. */ BL::ViewLayer::aovs_iterator b_aov_iter; for (b_view_layer.aovs.begin(b_aov_iter); b_aov_iter != b_view_layer.aovs.end(); ++b_aov_iter) { diff --git a/intern/cycles/cmake/external_libs.cmake b/intern/cycles/cmake/external_libs.cmake index 5854c80fb9f..ab709b1ca10 100644 --- a/intern/cycles/cmake/external_libs.cmake +++ b/intern/cycles/cmake/external_libs.cmake @@ -104,6 +104,10 @@ if(CYCLES_STANDALONE_REPOSITORY) else() unset(_cycles_lib_dir) endif() +else() + if(EXISTS ${LIBDIR}) + set(_cycles_lib_dir ${LIBDIR}) + endif() endif() ########################################################################### @@ -269,6 +273,25 @@ if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OSL) endif() endif() +########################################################################### +# OpenPGL +########################################################################### + +if(WITH_CYCLES_PATH_GUIDING) + if(EXISTS ${_cycles_lib_dir}) + set(openpgl_DIR ${_cycles_lib_dir}/openpgl/lib/cmake/openpgl) + endif() + + find_package(openpgl QUIET) + if(openpgl_FOUND) + get_target_property(OPENPGL_LIBRARIES openpgl::openpgl LOCATION) + get_target_property(OPENPGL_INCLUDE_DIR openpgl::openpgl INTERFACE_INCLUDE_DIRECTORIES) + else() + set(WITH_CYCLES_PATH_GUIDING OFF) + message(STATUS "OpenPGL not found, disabling WITH_CYCLES_PATH_GUIDING") + endif() +endif() + ########################################################################### # OpenColorIO ########################################################################### diff --git a/intern/cycles/cmake/macros.cmake b/intern/cycles/cmake/macros.cmake index 786d061599f..4ad438c65f9 100644 --- a/intern/cycles/cmake/macros.cmake +++ b/intern/cycles/cmake/macros.cmake @@ -118,6 +118,9 @@ macro(cycles_external_libraries_append libraries) if(WITH_ALEMBIC) list(APPEND ${libraries} ${ALEMBIC_LIBRARIES}) endif() + if(WITH_PATH_GUIDING) + target_link_libraries(${target} ${OPENPGL_LIBRARIES}) + endif() list(APPEND ${libraries} ${OPENIMAGEIO_LIBRARIES} diff --git a/intern/cycles/device/cpu/device.cpp b/intern/cycles/device/cpu/device.cpp index 5ae0130785b..9b249063aec 100644 --- a/intern/cycles/device/cpu/device.cpp +++ b/intern/cycles/device/cpu/device.cpp @@ -7,6 +7,7 @@ /* Used for `info.denoisers`. */ /* TODO(sergey): The denoisers are probably to be moved completely out of the device into their * own class. But until then keep API consistent with how it used to work before. */ +#include "util/guiding.h" #include "util/openimagedenoise.h" CCL_NAMESPACE_BEGIN @@ -27,6 +28,12 @@ void device_cpu_info(vector &devices) info.has_osl = true; info.has_nanovdb = true; info.has_profiling = true; + if (guiding_supported()) { + info.has_guiding = true; + } + else { + info.has_guiding = false; + } if (openimagedenoise_supported()) { info.denoisers |= DENOISER_OPENIMAGEDENOISE; } diff --git a/intern/cycles/device/cpu/device_impl.cpp b/intern/cycles/device/cpu/device_impl.cpp index a2b8d1cbbfa..3d0f3195f74 100644 --- a/intern/cycles/device/cpu/device_impl.cpp +++ b/intern/cycles/device/cpu/device_impl.cpp @@ -38,6 +38,7 @@ #include "util/debug.h" #include "util/foreach.h" #include "util/function.h" +#include "util/guiding.h" #include "util/log.h" #include "util/map.h" #include "util/openimagedenoise.h" @@ -278,6 +279,23 @@ void CPUDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) Device::build_bvh(bvh, progress, refit); } +void *CPUDevice::get_guiding_device() const +{ +#ifdef WITH_PATH_GUIDING + if (!guiding_device) { + if (guiding_device_type() == 8) { + guiding_device = make_unique(PGL_DEVICE_TYPE_CPU_8); + } + else if (guiding_device_type() == 4) { + guiding_device = make_unique(PGL_DEVICE_TYPE_CPU_4); + } + } + return guiding_device.get(); +#else + return nullptr; +#endif +} + void CPUDevice::get_cpu_kernel_thread_globals( vector &kernel_thread_globals) { diff --git a/intern/cycles/device/cpu/device_impl.h b/intern/cycles/device/cpu/device_impl.h index e7e77f18194..cbc78d3247a 100644 --- a/intern/cycles/device/cpu/device_impl.h +++ b/intern/cycles/device/cpu/device_impl.h @@ -26,6 +26,9 @@ #include "kernel/osl/globals.h" // clang-format on +#include "util/guiding.h" +#include "util/unique_ptr.h" + CCL_NAMESPACE_BEGIN class CPUDevice : public Device { @@ -42,6 +45,9 @@ class CPUDevice : public Device { RTCScene embree_scene = NULL; RTCDevice embree_device; #endif +#ifdef WITH_PATH_GUIDING + mutable unique_ptr guiding_device; +#endif CPUDevice(const DeviceInfo &info_, Stats &stats_, Profiler &profiler_); ~CPUDevice(); @@ -72,6 +78,8 @@ class CPUDevice : public Device { void build_bvh(BVH *bvh, Progress &progress, bool refit) override; + void *get_guiding_device() const override; + virtual void get_cpu_kernel_thread_globals( vector &kernel_thread_globals) override; virtual void *get_cpu_osl_memory() override; diff --git a/intern/cycles/device/cpu/kernel_thread_globals.cpp b/intern/cycles/device/cpu/kernel_thread_globals.cpp index 99af1525d92..90880f5e5f7 100644 --- a/intern/cycles/device/cpu/kernel_thread_globals.cpp +++ b/intern/cycles/device/cpu/kernel_thread_globals.cpp @@ -14,19 +14,23 @@ CPUKernelThreadGlobals::CPUKernelThreadGlobals(const KernelGlobalsCPU &kernel_gl Profiler &cpu_profiler) : KernelGlobalsCPU(kernel_globals), cpu_profiler_(cpu_profiler) { - reset_runtime_memory(); + clear_runtime_pointers(); #ifdef WITH_OSL OSLGlobals::thread_init(this, static_cast(osl_globals_memory)); #else (void)osl_globals_memory; #endif + +#ifdef WITH_PATH_GUIDING + opgl_path_segment_storage = new openpgl::cpp::PathSegmentStorage(); +#endif } CPUKernelThreadGlobals::CPUKernelThreadGlobals(CPUKernelThreadGlobals &&other) noexcept : KernelGlobalsCPU(std::move(other)), cpu_profiler_(other.cpu_profiler_) { - other.reset_runtime_memory(); + other.clear_runtime_pointers(); } CPUKernelThreadGlobals::~CPUKernelThreadGlobals() @@ -34,6 +38,12 @@ CPUKernelThreadGlobals::~CPUKernelThreadGlobals() #ifdef WITH_OSL OSLGlobals::thread_free(this); #endif + +#ifdef WITH_PATH_GUIDING + delete opgl_path_segment_storage; + delete opgl_surface_sampling_distribution; + delete opgl_volume_sampling_distribution; +#endif } CPUKernelThreadGlobals &CPUKernelThreadGlobals::operator=(CPUKernelThreadGlobals &&other) @@ -44,16 +54,25 @@ CPUKernelThreadGlobals &CPUKernelThreadGlobals::operator=(CPUKernelThreadGlobals *static_cast(this) = *static_cast(&other); - other.reset_runtime_memory(); + other.clear_runtime_pointers(); return *this; } -void CPUKernelThreadGlobals::reset_runtime_memory() +void CPUKernelThreadGlobals::clear_runtime_pointers() { #ifdef WITH_OSL osl = nullptr; #endif + +#ifdef WITH_PATH_GUIDING + opgl_sample_data_storage = nullptr; + opgl_guiding_field = nullptr; + + opgl_path_segment_storage = nullptr; + opgl_surface_sampling_distribution = nullptr; + opgl_volume_sampling_distribution = nullptr; +#endif } void CPUKernelThreadGlobals::start_profiling() diff --git a/intern/cycles/device/cpu/kernel_thread_globals.h b/intern/cycles/device/cpu/kernel_thread_globals.h index 96d2bd9e165..2462eb4cb2c 100644 --- a/intern/cycles/device/cpu/kernel_thread_globals.h +++ b/intern/cycles/device/cpu/kernel_thread_globals.h @@ -36,7 +36,7 @@ class CPUKernelThreadGlobals : public KernelGlobalsCPU { void stop_profiling(); protected: - void reset_runtime_memory(); + void clear_runtime_pointers(); Profiler &cpu_profiler_; }; diff --git a/intern/cycles/device/device.cpp b/intern/cycles/device/device.cpp index ace6ed517f5..6aef5458246 100644 --- a/intern/cycles/device/device.cpp +++ b/intern/cycles/device/device.cpp @@ -352,6 +352,7 @@ DeviceInfo Device::get_multi_device(const vector &subdevices, info.has_nanovdb = true; info.has_osl = true; + info.has_guiding = true; info.has_profiling = true; info.has_peer_memory = false; info.use_metalrt = false; @@ -399,6 +400,7 @@ DeviceInfo Device::get_multi_device(const vector &subdevices, /* Accumulate device info. */ info.has_nanovdb &= device.has_nanovdb; info.has_osl &= device.has_osl; + info.has_guiding &= device.has_guiding; info.has_profiling &= device.has_profiling; info.has_peer_memory |= device.has_peer_memory; info.use_metalrt |= device.use_metalrt; diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index cdb13ca0a97..2e4d18241cf 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -66,6 +66,7 @@ class DeviceInfo { bool display_device; /* GPU is used as a display device. */ bool has_nanovdb; /* Support NanoVDB volumes. */ bool has_osl; /* Support Open Shading Language. */ + bool has_guiding; /* Support path guiding. */ bool has_profiling; /* Supports runtime collection of profiling info. */ bool has_peer_memory; /* GPU has P2P access to memory of another GPU. */ bool has_gpu_queue; /* Device supports GPU queue. */ @@ -84,6 +85,7 @@ class DeviceInfo { display_device = false; has_nanovdb = false; has_osl = false; + has_guiding = false; has_profiling = false; has_peer_memory = false; has_gpu_queue = false; @@ -217,6 +219,15 @@ class Device { return false; } + /* Guiding */ + + /* Returns path guiding device handle. */ + virtual void *get_guiding_device() const + { + LOG(ERROR) << "Request guiding field from a device which does not support it."; + return nullptr; + } + /* Buffer denoising. */ /* Returns true if task is fully handled. */ diff --git a/intern/cycles/integrator/CMakeLists.txt b/intern/cycles/integrator/CMakeLists.txt index 9722003083e..ef2a07854ec 100644 --- a/intern/cycles/integrator/CMakeLists.txt +++ b/intern/cycles/integrator/CMakeLists.txt @@ -65,6 +65,12 @@ if(WITH_OPENIMAGEDENOISE) ) endif() +if(WITH_CYCLES_PATH_GUIDING) + list(APPEND LIB + ${OPENPGL_LIBRARIES} + ) +endif() + include_directories(${INC}) include_directories(SYSTEM ${INC_SYS}) diff --git a/intern/cycles/integrator/guiding.h b/intern/cycles/integrator/guiding.h new file mode 100644 index 00000000000..b7d7e2fe51e --- /dev/null +++ b/intern/cycles/integrator/guiding.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#pragma once + +#include "kernel/types.h" + +CCL_NAMESPACE_BEGIN + +struct GuidingParams { + /* The subset of path guiding parameters that can trigger a creation/rebuild + * of the guiding field. */ + bool use = false; + bool use_surface_guiding = false; + bool use_volume_guiding = false; + + GuidingDistributionType type = GUIDING_TYPE_PARALLAX_AWARE_VMM; + int training_samples = 128; + bool deterministic = false; + + GuidingParams() = default; + + bool modified(const GuidingParams &other) const + { + return !((use == other.use) && (use_surface_guiding == other.use_surface_guiding) && + (use_volume_guiding == other.use_volume_guiding) && (type == other.type) && + (training_samples == other.training_samples) && + (deterministic == other.deterministic)); + } +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index 3ec7b601d9f..56b8e46ebda 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -185,11 +185,25 @@ void PathTrace::render_pipeline(RenderWork render_work) rebalance(render_work); + /* Prepare all per-thread guiding structures before we start with the next rendering + * iteration/progression. */ + const bool use_guiding = device_scene_->data.integrator.use_guiding; + if (use_guiding) { + guiding_prepare_structures(); + } + path_trace(render_work); if (render_cancel_.is_requested) { return; } + /* Update the guiding field using the training data/samples collected during the rendering + * iteration/progression. */ + const bool train_guiding = device_scene_->data.integrator.train_guiding; + if (use_guiding && train_guiding) { + guiding_update_structures(); + } + adaptive_sample(render_work); if (render_cancel_.is_requested) { return; @@ -1241,4 +1255,119 @@ string PathTrace::full_report() const return result; } +void PathTrace::set_guiding_params(const GuidingParams &guiding_params, const bool reset) +{ +#ifdef WITH_PATH_GUIDING + if (guiding_params_.modified(guiding_params)) { + guiding_params_ = guiding_params; + + if (guiding_params_.use) { + PGLFieldArguments field_args; + switch (guiding_params_.type) { + default: + /* Parallax-aware von Mises-Fisher mixture models. */ + case GUIDING_TYPE_PARALLAX_AWARE_VMM: { + pglFieldArgumentsSetDefaults( + field_args, + PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE, + PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_PARALLAX_AWARE_VMM); + break; + } + /* Directional quad-trees. */ + case GUIDING_TYPE_DIRECTIONAL_QUAD_TREE: { + pglFieldArgumentsSetDefaults( + field_args, + PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE, + PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_QUADTREE); + break; + } + /* von Mises-Fisher mixture models. */ + case GUIDING_TYPE_VMM: { + pglFieldArgumentsSetDefaults( + field_args, + PGL_SPATIAL_STRUCTURE_TYPE::PGL_SPATIAL_STRUCTURE_KDTREE, + PGL_DIRECTIONAL_DISTRIBUTION_TYPE::PGL_DIRECTIONAL_DISTRIBUTION_VMM); + break; + } + } +# if OPENPGL_VERSION_MINOR >= 4 + field_args.deterministic = guiding_params.deterministic; +# endif + openpgl::cpp::Device *guiding_device = static_cast( + device_->get_guiding_device()); + if (guiding_device) { + guiding_sample_data_storage_ = make_unique(); + guiding_field_ = make_unique(guiding_device, field_args); + } + else { + guiding_sample_data_storage_ = nullptr; + guiding_field_ = nullptr; + } + } + else { + guiding_sample_data_storage_ = nullptr; + guiding_field_ = nullptr; + } + } + else if (reset) { + if (guiding_field_) { + guiding_field_->Reset(); + } + } +#endif +} + +void PathTrace::guiding_prepare_structures() +{ +#ifdef WITH_PATH_GUIDING + const bool train = (guiding_params_.training_samples == 0) || + (guiding_field_->GetIteration() < guiding_params_.training_samples); + + for (auto &&path_trace_work : path_trace_works_) { + path_trace_work->guiding_init_kernel_globals( + guiding_field_.get(), guiding_sample_data_storage_.get(), train); + } + + if (train) { + /* For training the guiding distribution we need to force the number of samples + * per update to be limited, for reproducible results and reasonable training size. + * + * Idea: we could stochastically discard samples with a probability of 1/num_samples_per_update + * we can then update only after the num_samples_per_update iterations are rendered. */ + render_scheduler_.set_limit_samples_per_update(4); + } + else { + render_scheduler_.set_limit_samples_per_update(0); + } +#endif +} + +void PathTrace::guiding_update_structures() +{ +#ifdef WITH_PATH_GUIDING + VLOG_WORK << "Update path guiding structures"; + + VLOG_DEBUG << "Number of surface samples: " << guiding_sample_data_storage_->GetSizeSurface(); + VLOG_DEBUG << "Number of volume samples: " << guiding_sample_data_storage_->GetSizeVolume(); + + const size_t num_valid_samples = guiding_sample_data_storage_->GetSizeSurface() + + guiding_sample_data_storage_->GetSizeVolume(); + + /* we wait until we have at least 1024 samples */ + if (num_valid_samples >= 1024) { +# if OPENPGL_VERSION_MINOR < 4 + const size_t num_samples = 1; + guiding_field_->Update(*guiding_sample_data_storage_, num_samples); +# else + guiding_field_->Update(*guiding_sample_data_storage_); +# endif + guiding_update_count++; + + VLOG_DEBUG << "Path guiding field valid: " << guiding_field_->Validate(); + + guiding_sample_data_storage_->Clear(); + } +#endif +} + CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index 59382b51d23..d3a238696fd 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -4,11 +4,15 @@ #pragma once #include "integrator/denoiser.h" +#include "integrator/guiding.h" #include "integrator/pass_accessor.h" #include "integrator/path_trace_work.h" #include "integrator/work_balancer.h" + #include "session/buffers.h" + #include "util/function.h" +#include "util/guiding.h" #include "util/thread.h" #include "util/unique_ptr.h" #include "util/vector.h" @@ -89,6 +93,10 @@ class PathTrace { * Use this to configure the adaptive sampler before rendering any samples. */ void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling); + /* Set the parameters for guiding. + * Use to setup the guiding structures before each rendering iteration.*/ + void set_guiding_params(const GuidingParams ¶ms, const bool reset); + /* Sets output driver for render buffer output. */ void set_output_driver(unique_ptr driver); @@ -205,6 +213,15 @@ class PathTrace { void write_tile_buffer(const RenderWork &render_work); void finalize_full_buffer_on_disk(const RenderWork &render_work); + /* Updates/initializes the guiding structures after a rendering iteration. + * The structures are updated using the training data/samples generated during the previous + * rendering iteration */ + void guiding_update_structures(); + + /* Prepares the per-kernel thread related guiding structures (e.g., PathSegmentStorage, + * pointers to the global Field and SegmentStorage)*/ + void guiding_prepare_structures(); + /* Get number of samples in the current state of the render buffers. */ int get_num_samples_in_buffer(); @@ -265,6 +282,22 @@ class PathTrace { /* Denoiser device descriptor which holds the denoised big tile for multi-device workloads. */ unique_ptr big_tile_denoise_work_; +#ifdef WITH_PATH_GUIDING + /* Guiding related attributes */ + GuidingParams guiding_params_; + + /* The guiding field which holds the representation of the incident radiance field for the + * complete scene. */ + unique_ptr guiding_field_; + + /* The storage container which holds the training data/samples generated during the last + * rendering iteration. */ + unique_ptr guiding_sample_data_storage_; + + /* The number of already performed training iterations for the guiding field.*/ + int guiding_update_count = 0; +#endif + /* State which is common for all the steps of the render work. * Is brought up to date in the `render()` call and is accessed from all the steps involved into * rendering the work. */ diff --git a/intern/cycles/integrator/path_trace_work.h b/intern/cycles/integrator/path_trace_work.h index 737d6babc08..e31a6ef8819 100644 --- a/intern/cycles/integrator/path_trace_work.h +++ b/intern/cycles/integrator/path_trace_work.h @@ -140,6 +140,13 @@ class PathTraceWork { return device_; } +#ifdef WITH_PATH_GUIDING + /* Initializes the per-thread guiding kernel data. */ + virtual void guiding_init_kernel_globals(void *, void *, const bool) + { + } +#endif + protected: PathTraceWork(Device *device, Film *film, diff --git a/intern/cycles/integrator/path_trace_work_cpu.cpp b/intern/cycles/integrator/path_trace_work_cpu.cpp index 518ef3185f9..d5ac830db58 100644 --- a/intern/cycles/integrator/path_trace_work_cpu.cpp +++ b/intern/cycles/integrator/path_trace_work_cpu.cpp @@ -6,6 +6,7 @@ #include "device/cpu/kernel.h" #include "device/device.h" +#include "kernel/film/write.h" #include "kernel/integrator/path_state.h" #include "integrator/pass_accessor_cpu.h" @@ -145,6 +146,13 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobalsCPU *kernel_glo kernels_.integrator_megakernel(kernel_globals, state, render_buffer); +#ifdef WITH_PATH_GUIDING + if (kernel_globals->data.integrator.train_guiding) { + /* Push the generated sample data to the global sample data storage. */ + guiding_push_sample_data_to_global_storage(kernel_globals, state, render_buffer); + } +#endif + if (shadow_catcher_state) { kernels_.integrator_megakernel(kernel_globals, shadow_catcher_state, render_buffer); } @@ -276,4 +284,106 @@ void PathTraceWorkCPU::cryptomatte_postproces() }); } +#ifdef WITH_PATH_GUIDING +/* Note: It seems that this is called before every rendering iteration/progression and not once per + * rendering. May be we find a way to call it only once per rendering. */ +void PathTraceWorkCPU::guiding_init_kernel_globals(void *guiding_field, + void *sample_data_storage, + const bool train) +{ + /* Linking the global guiding structures (e.g., Field and SampleStorage) to the per-thread + * kernel globals. */ + for (int thread_index = 0; thread_index < kernel_thread_globals_.size(); thread_index++) { + CPUKernelThreadGlobals &kg = kernel_thread_globals_[thread_index]; + openpgl::cpp::Field *field = (openpgl::cpp::Field *)guiding_field; + + /* Allocate sampling distributions. */ + kg.opgl_guiding_field = field; + +# if PATH_GUIDING_LEVEL >= 4 + if (kg.opgl_surface_sampling_distribution) { + delete kg.opgl_surface_sampling_distribution; + kg.opgl_surface_sampling_distribution = nullptr; + } + if (kg.opgl_volume_sampling_distribution) { + delete kg.opgl_volume_sampling_distribution; + kg.opgl_volume_sampling_distribution = nullptr; + } + + if (field) { + kg.opgl_surface_sampling_distribution = new openpgl::cpp::SurfaceSamplingDistribution(field); + kg.opgl_volume_sampling_distribution = new openpgl::cpp::VolumeSamplingDistribution(field); + } +# endif + + /* Reserve storage for training. */ + kg.data.integrator.train_guiding = train; + kg.opgl_sample_data_storage = (openpgl::cpp::SampleStorage *)sample_data_storage; + + if (train) { + kg.opgl_path_segment_storage->Reserve(kg.data.integrator.transparent_max_bounce + + kg.data.integrator.max_bounce + 3); + kg.opgl_path_segment_storage->Clear(); + } + } +} + +void PathTraceWorkCPU::guiding_push_sample_data_to_global_storage( + KernelGlobalsCPU *kg, IntegratorStateCPU *state, ccl_global float *ccl_restrict render_buffer) +{ +# ifdef WITH_CYCLES_DEBUG + if (VLOG_WORK_IS_ON) { + /* Check if the generated path segments contain valid values. */ + const bool validSegments = kg->opgl_path_segment_storage->ValidateSegments(); + if (!validSegments) { + VLOG_WORK << "Guiding: invalid path segments!"; + } + } + + /* Write debug render pass to validate it matches combined pass. */ + pgl_vec3f pgl_final_color = kg->opgl_path_segment_storage->CalculatePixelEstimate(false); + const uint32_t render_pixel_index = INTEGRATOR_STATE(state, path, render_pixel_index); + const uint64_t render_buffer_offset = (uint64_t)render_pixel_index * + kernel_data.film.pass_stride; + ccl_global float *buffer = render_buffer + render_buffer_offset; + float3 final_color = make_float3(pgl_final_color.x, pgl_final_color.y, pgl_final_color.z); + if (kernel_data.film.pass_guiding_color != PASS_UNUSED) { + film_write_pass_float3(buffer + kernel_data.film.pass_guiding_color, final_color); + } +# else + (void)state; + (void)render_buffer; +# endif + + /* Convert the path segment representation of the random walk into radiance samples. */ +# if PATH_GUIDING_LEVEL >= 2 + const bool use_direct_light = kernel_data.integrator.use_guiding_direct_light; + const bool use_mis_weights = kernel_data.integrator.use_guiding_mis_weights; + kg->opgl_path_segment_storage->PrepareSamples( + false, nullptr, use_mis_weights, use_direct_light, false); +# endif + +# ifdef WITH_CYCLES_DEBUG + /* Check if the training/radiance samples generated py the path segment storage are valid.*/ + if (VLOG_WORK_IS_ON) { + const bool validSamples = kg->opgl_path_segment_storage->ValidateSamples(); + if (!validSamples) { + VLOG_WORK + << "Guiding: path segment storage generated/contains invalid radiance/training samples!"; + } + } +# endif + +# if PATH_GUIDING_LEVEL >= 3 + /* Push radiance samples from current random walk/path to the global sample storage. */ + size_t num_samples = 0; + const openpgl::cpp::SampleData *samples = kg->opgl_path_segment_storage->GetSamples(num_samples); + kg->opgl_sample_data_storage->AddSamples(samples, num_samples); +# endif + + /* Clear storage for the current path, to be ready for the next path. */ + kg->opgl_path_segment_storage->Clear(); +} +#endif + CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/path_trace_work_cpu.h b/intern/cycles/integrator/path_trace_work_cpu.h index 5a0918aecec..7adb00b4d9d 100644 --- a/intern/cycles/integrator/path_trace_work_cpu.h +++ b/intern/cycles/integrator/path_trace_work_cpu.h @@ -16,6 +16,7 @@ CCL_NAMESPACE_BEGIN struct KernelWorkTile; struct KernelGlobalsCPU; +struct IntegratorStateCPU; class CPUKernels; @@ -50,6 +51,22 @@ class PathTraceWorkCPU : public PathTraceWork { virtual int adaptive_sampling_converge_filter_count_active(float threshold, bool reset) override; virtual void cryptomatte_postproces() override; +#ifdef WITH_PATH_GUIDING + /* Intializes the per-thread guiding kernel data. The function sets the pointers to the + * global guiding field and the sample data storage as well es initializes the per-thread + * guided sampling distrubtions (e.g., SurfaceSamplingDistribution and + * VolumeSamplingDistribution). */ + void guiding_init_kernel_globals(void *guiding_field, + void *sample_data_storage, + const bool train) override; + + /* Pushes the collected training data/samples of a path to the global sample storage. + * This function is called at the end of a random walk/path generation. */ + void guiding_push_sample_data_to_global_storage(KernelGlobalsCPU *kernel_globals, + IntegratorStateCPU *state, + ccl_global float *ccl_restrict render_buffer); +#endif + protected: /* Core path tracing routine. Renders given work time on the given queue. */ void render_samples_full_pipeline(KernelGlobalsCPU *kernel_globals, diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp index e4676bd059c..2e05dbbaf6e 100644 --- a/intern/cycles/integrator/render_scheduler.cpp +++ b/intern/cycles/integrator/render_scheduler.cpp @@ -45,6 +45,11 @@ void RenderScheduler::set_denoiser_params(const DenoiseParams ¶ms) denoiser_params_ = params; } +void RenderScheduler::set_limit_samples_per_update(const int limit_samples) +{ + limit_samples_per_update_ = limit_samples; +} + void RenderScheduler::set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling) { adaptive_sampling_ = adaptive_sampling; @@ -760,7 +765,13 @@ int RenderScheduler::calculate_num_samples_per_update() const const double update_interval_in_seconds = guess_display_update_interval_in_seconds(); - return max(int(num_samples_in_second * update_interval_in_seconds), 1); + int num_samples_per_update = max(int(num_samples_in_second * update_interval_in_seconds), 1); + + if (limit_samples_per_update_) { + num_samples_per_update = min(limit_samples_per_update_, num_samples_per_update); + } + + return num_samples_per_update; } int RenderScheduler::get_start_sample_to_path_trace() const @@ -808,7 +819,7 @@ int RenderScheduler::get_num_samples_to_path_trace() const return 1; } - const int num_samples_per_update = calculate_num_samples_per_update(); + int num_samples_per_update = calculate_num_samples_per_update(); const int path_trace_start_sample = get_start_sample_to_path_trace(); /* Round number of samples to a power of two, so that division of path states into tiles goes in diff --git a/intern/cycles/integrator/render_scheduler.h b/intern/cycles/integrator/render_scheduler.h index dce876d44bd..a0ab17b3794 100644 --- a/intern/cycles/integrator/render_scheduler.h +++ b/intern/cycles/integrator/render_scheduler.h @@ -187,6 +187,8 @@ class RenderScheduler { * times, and so on. */ string full_report() const; + void set_limit_samples_per_update(const int limit_samples); + protected: /* Check whether all work has been scheduled and time limit was not exceeded. * @@ -450,6 +452,10 @@ class RenderScheduler { * (quadratic dependency from the resolution divider): resolution divider of 2 brings render time * down by a factor of 4. */ int calculate_resolution_divider_for_time(double desired_time, double actual_time); + + /* If the number of samples per rendering progression should be limited because of path guiding + * being activated or is still inside its training phase */ + int limit_samples_per_update_ = 0; }; int calculate_resolution_divider_for_resolution(int width, int height, int resolution); diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index ee0cefa823e..565e50b3108 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -243,6 +243,7 @@ set(SRC_KERNEL_INTEGRATOR_HEADERS integrator/intersect_shadow.h integrator/intersect_subsurface.h integrator/intersect_volume_stack.h + integrator/guiding.h integrator/megakernel.h integrator/mnee.h integrator/path_state.h diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h index 807d0650fc3..1e9e25f2f9d 100644 --- a/intern/cycles/kernel/data_template.h +++ b/intern/cycles/kernel/data_template.h @@ -133,6 +133,10 @@ KERNEL_STRUCT_MEMBER(film, int, pass_bake_primitive) KERNEL_STRUCT_MEMBER(film, int, pass_bake_differential) /* Shadow catcher. */ KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher) +/* Path Guiding */ +KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color) +KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability) +KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness) /* Padding. */ KERNEL_STRUCT_MEMBER(film, int, pad1) KERNEL_STRUCT_MEMBER(film, int, pad2) @@ -190,8 +194,17 @@ KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher) KERNEL_STRUCT_MEMBER(integrator, int, filter_closures) /* MIS debugging. */ KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type) -/* Padding */ -KERNEL_STRUCT_MEMBER(integrator, int, pad1) + +/* Path Guiding */ +KERNEL_STRUCT_MEMBER(integrator, float, surface_guiding_probability) +KERNEL_STRUCT_MEMBER(integrator, float, volume_guiding_probability) +KERNEL_STRUCT_MEMBER(integrator, int, guiding_distribution_type) +KERNEL_STRUCT_MEMBER(integrator, int, use_guiding) +KERNEL_STRUCT_MEMBER(integrator, int, train_guiding) +KERNEL_STRUCT_MEMBER(integrator, int, use_surface_guiding) +KERNEL_STRUCT_MEMBER(integrator, int, use_volume_guiding) +KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_direct_light) +KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights) KERNEL_STRUCT_END(KernelIntegrator) /* SVM. For shader specialization. */ diff --git a/intern/cycles/kernel/device/cpu/globals.h b/intern/cycles/kernel/device/cpu/globals.h index 309afae412e..f7f1a36b2a7 100644 --- a/intern/cycles/kernel/device/cpu/globals.h +++ b/intern/cycles/kernel/device/cpu/globals.h @@ -9,6 +9,8 @@ #include "kernel/types.h" #include "kernel/util/profiling.h" +#include "util/guiding.h" + CCL_NAMESPACE_BEGIN /* On the CPU, we pass along the struct KernelGlobals to nearly everywhere in @@ -43,9 +45,20 @@ typedef struct KernelGlobalsCPU { #ifdef __OSL__ /* On the CPU, we also have the OSL globals here. Most data structures are shared * with SVM, the difference is in the shaders and object/mesh attributes. */ - OSLGlobals *osl; - OSLShadingSystem *osl_ss; - OSLThreadData *osl_tdata; + OSLGlobals *osl = nullptr; + OSLShadingSystem *osl_ss = nullptr; + OSLThreadData *osl_tdata = nullptr; +#endif + +#ifdef __PATH_GUIDING__ + /* Pointers to global data structures. */ + openpgl::cpp::SampleStorage *opgl_sample_data_storage = nullptr; + openpgl::cpp::Field *opgl_guiding_field = nullptr; + + /* Local data structures owned by the thread. */ + openpgl::cpp::PathSegmentStorage *opgl_path_segment_storage = nullptr; + openpgl::cpp::SurfaceSamplingDistribution *opgl_surface_sampling_distribution = nullptr; + openpgl::cpp::VolumeSamplingDistribution *opgl_volume_sampling_distribution = nullptr; #endif /* **** Run-time data **** */ diff --git a/intern/cycles/kernel/integrator/guiding.h b/intern/cycles/kernel/integrator/guiding.h new file mode 100644 index 00000000000..5d09e5ceac4 --- /dev/null +++ b/intern/cycles/kernel/integrator/guiding.h @@ -0,0 +1,542 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#pragma once + +#include "kernel/closure/alloc.h" +#include "kernel/closure/bsdf.h" +#include "kernel/film/write.h" + +CCL_NAMESPACE_BEGIN + +/* Utilities. */ + +#if defined(__PATH_GUIDING__) +static pgl_vec3f guiding_vec3f(const float3 v) +{ + return openpgl::cpp::Vector3(v.x, v.y, v.z); +} + +static pgl_point3f guiding_point3f(const float3 v) +{ + return openpgl::cpp::Point3(v.x, v.y, v.z); +} +#endif + +/* Path recording for guiding. */ + +/* Record Surface Interactions */ + +/* Records/Adds a new path segment with the current path vertex on a surface. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_surface_bounce. */ +ccl_device_forceinline void guiding_record_surface_segment(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(sd->P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(sd->I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the surface scattering event at the current vertex position of the segment.*/ +ccl_device_forceinline void guiding_record_surface_bounce(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + const Spectrum weight, + const float pdf, + const float3 N, + const float3 omega_in, + const float2 roughness, + const float eta) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float min_roughness = safe_sqrtf(fminf(roughness.x, roughness.y)); + const bool is_delta = (min_roughness == 0.0f); + const float3 weight_rgb = spectrum_to_rgb(weight); + const float3 normal = clamp(N, -one_float3(), one_float3()); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3())); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, is_delta); + openpgl::cpp::SetEta(state->guiding.path_segment, eta); + openpgl::cpp::SetRoughness(state->guiding.path_segment, min_roughness); +#endif +} + +/* Records the emission at the current surface intersection (physical or virtual) */ +ccl_device_forceinline void guiding_record_surface_emission(KernelGlobals kg, + IntegratorState state, + const Spectrum Le, + const float mis_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 Le_rgb = spectrum_to_rgb(Le); + + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb)); + openpgl::cpp::SetMiWeight(state->guiding.path_segment, mis_weight); +#endif +} + +/* Record BSSRDF Interactions */ + +/* Records/Adds a new path segment where the vertex position is the point of entry + * of the sub surface scattering boundary. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_bssrdf_weight and guiding_record_bssrdf_bounce. */ +ccl_device_forceinline void guiding_record_bssrdf_segment(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 I) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the transmission of the path at the point of entry while passing + * the surface boundary.*/ +ccl_device_forceinline void guiding_record_bssrdf_weight(KernelGlobals kg, + IntegratorState state, + const Spectrum weight, + const Spectrum albedo) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + /* Note albedo left out here, will be included in guiding_record_bssrdf_bounce. */ + const float3 weight_rgb = spectrum_to_rgb(safe_divide_color(weight, albedo)); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(zero_float3())); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, false); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f); + openpgl::cpp::SetRoughness(state->guiding.path_segment, 1.0f); +#endif +} + +/* Records the direction at the point of entry the path takes when sampling the SSS contribution. + * If not terminated this function is usually followed by a call of + * guiding_record_volume_transmission to record the transmittance between the point of entry and + * the point of exit.*/ +ccl_device_forceinline void guiding_record_bssrdf_bounce(KernelGlobals kg, + IntegratorState state, + const float pdf, + const float3 N, + const float3 omega_in, + const Spectrum weight, + const Spectrum albedo) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 normal = clamp(N, -one_float3(), one_float3()); + const float3 weight_rgb = spectrum_to_rgb(weight * albedo); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); +#endif +} + +/* Record Volume Interactions */ + +/* Records/Adds a new path segment with the current path vertex being inside a volume. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_volume_bounce. */ +ccl_device_forceinline void guiding_record_volume_segment(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 I) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the volume scattering event at the current vertex position of the segment.*/ +ccl_device_forceinline void guiding_record_volume_bounce(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + const Spectrum weight, + const float pdf, + const float3 omega_in, + const float roughness) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 weight_rgb = spectrum_to_rgb(weight); + const float3 normal = make_float3(0.0f, 0.0f, 1.0f); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3())); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, false); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.f); + openpgl::cpp::SetRoughness(state->guiding.path_segment, roughness); +#endif +} + +/* Records the transmission (a.k.a. transmittance weight) between the current path segment + * and the next one, when the path is inside or passes a volume.*/ +ccl_device_forceinline void guiding_record_volume_transmission(KernelGlobals kg, + IntegratorState state, + const float3 transmittance_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + // TODO (sherholz): need to find a better way to avoid this check + if ((transmittance_weight[0] < 0.f || !std::isfinite(transmittance_weight[0]) || + std::isnan(transmittance_weight[0])) || + (transmittance_weight[1] < 0.f || !std::isfinite(transmittance_weight[1]) || + std::isnan(transmittance_weight[1])) || + (transmittance_weight[2] < 0.f || !std::isfinite(transmittance_weight[2]) || + std::isnan(transmittance_weight[2]))) { + } + else { + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, + guiding_vec3f(transmittance_weight)); + } + } +#endif +} + +/* Records the emission of a volume at the vertex of the current path segment. */ +ccl_device_forceinline void guiding_record_volume_emission(KernelGlobals kg, + IntegratorState state, + const Spectrum Le) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + const float3 Le_rgb = spectrum_to_rgb(Le); + + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb)); + openpgl::cpp::SetMiWeight(state->guiding.path_segment, 1.0f); + } +#endif +} + +/* Record Light Interactions */ + +/* Adds a pseudo path vertex/segment when intersecting a virtual light source. + * (e.g., area, sphere, or disk light). This call is often followed + * a call of guiding_record_surface_emission, if the intersected light source + * emits light in the direction of tha path. */ +ccl_device_forceinline void guiding_record_light_surface_segment( + KernelGlobals kg, IntegratorState state, ccl_private const Intersection *ccl_restrict isect) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + const float3 ray_P = INTEGRATOR_STATE(state, ray, P); + const float3 ray_D = INTEGRATOR_STATE(state, ray, D); + const float3 P = ray_P + isect->t * ray_D; + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(ray_D)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, 1.0f); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f); +#endif +} + +/* Records/Adds a final path segment when the path leaves the scene and + * intersects with a background light (e.g., background color, + * distant light, or env map). The vertex for this segment is placed along + * the current ray far out the scene.*/ +ccl_device_forceinline void guiding_record_background(KernelGlobals kg, + IntegratorState state, + const Spectrum L, + const float mis_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + const float3 L_rgb = spectrum_to_rgb(L); + const float3 ray_P = INTEGRATOR_STATE(state, ray, P); + const float3 ray_D = INTEGRATOR_STATE(state, ray, D); + const float3 P = ray_P + (1e6f) * ray_D; + const float3 normal = make_float3(0.0f, 0.0f, 1.0f); + + openpgl::cpp::PathSegment background_segment; + openpgl::cpp::SetPosition(&background_segment, guiding_vec3f(P)); + openpgl::cpp::SetNormal(&background_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionOut(&background_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetDirectContribution(&background_segment, guiding_vec3f(L_rgb)); + openpgl::cpp::SetMiWeight(&background_segment, mis_weight); + kg->opgl_path_segment_storage->AddSegment(background_segment); +#endif +} + +/* Records the scattered contribution of a next event estimation + * (i.e., a direct light estimate scattered at the current path vertex + * towards the previous vertex).*/ +ccl_device_forceinline void guiding_record_direct_light(KernelGlobals kg, + IntegratorShadowState state) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + if (state->shadow_path.path_segment) { + const Spectrum Lo = safe_divide_color(INTEGRATOR_STATE(state, shadow_path, throughput), + INTEGRATOR_STATE(state, shadow_path, unlit_throughput)); + + const float3 Lo_rgb = spectrum_to_rgb(Lo); + openpgl::cpp::AddScatteredContribution(state->shadow_path.path_segment, guiding_vec3f(Lo_rgb)); + } +#endif +} + +/* Record Russian Roulette */ +/* Records the probability of continuing the path at the current path segment. */ +ccl_device_forceinline void guiding_record_continuation_probability( + KernelGlobals kg, IntegratorState state, const float continuation_probability) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + openpgl::cpp::SetRussianRouletteProbability(state->guiding.path_segment, + continuation_probability); + } +#endif +} + +/* Path guiding debug render passes. */ + +/* Write a set of path guiding related debug information (e.g., guiding probability at first + * bounce) into separate rendering passes.*/ +ccl_device_forceinline void guiding_write_debug_passes(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + ccl_global float *ccl_restrict + render_buffer) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 +# ifdef WITH_CYCLES_DEBUG + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (INTEGRATOR_STATE(state, path, bounce) != 0) { + return; + } + + const uint32_t render_pixel_index = INTEGRATOR_STATE(state, path, render_pixel_index); + const uint64_t render_buffer_offset = (uint64_t)render_pixel_index * + kernel_data.film.pass_stride; + ccl_global float *buffer = render_buffer + render_buffer_offset; + + if (kernel_data.film.pass_guiding_probability != PASS_UNUSED) { + float guiding_prob = state->guiding.surface_guiding_sampling_prob; + film_write_pass_float(buffer + kernel_data.film.pass_guiding_probability, guiding_prob); + } + + if (kernel_data.film.pass_guiding_avg_roughness != PASS_UNUSED) { + float avg_roughness = 0.0f; + float sum_sample_weight = 0.0f; + for (int i = 0; i < sd->num_closure; i++) { + ccl_private const ShaderClosure *sc = &sd->closure[i]; + + if (!CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + continue; + } + avg_roughness += sc->sample_weight * bsdf_get_specular_roughness_squared(sc); + sum_sample_weight += sc->sample_weight; + } + + avg_roughness = avg_roughness > 0.f ? avg_roughness / sum_sample_weight : 0.f; + + film_write_pass_float(buffer + kernel_data.film.pass_guiding_avg_roughness, avg_roughness); + } +# endif +#endif +} + +/* Guided BSDFs */ + +ccl_device_forceinline bool guiding_bsdf_init(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 N, + ccl_private float &rand) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kg->opgl_surface_sampling_distribution->Init( + kg->opgl_guiding_field, guiding_point3f(P), rand, true)) { + kg->opgl_surface_sampling_distribution->ApplyCosineProduct(guiding_point3f(N)); + return true; + } +#endif + + return false; +} + +ccl_device_forceinline float guiding_bsdf_sample(KernelGlobals kg, + IntegratorState state, + const float2 rand_bsdf, + ccl_private float3 *omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + pgl_vec3f wo; + const pgl_point2f rand = openpgl::cpp::Point2(rand_bsdf.x, rand_bsdf.y); + const float pdf = kg->opgl_surface_sampling_distribution->SamplePDF(rand, wo); + *omega_in = make_float3(wo.x, wo.y, wo.z); + return pdf; +#else + return 0.0f; +#endif +} + +ccl_device_forceinline float guiding_bsdf_pdf(KernelGlobals kg, + IntegratorState state, + const float3 omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + return kg->opgl_surface_sampling_distribution->PDF(guiding_vec3f(omega_in)); +#else + return 0.0f; +#endif +} + +/* Guided Volume Phases */ + +ccl_device_forceinline bool guiding_phase_init(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 D, + const float g, + ccl_private float &rand) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kg->opgl_volume_sampling_distribution->Init( + kg->opgl_guiding_field, guiding_point3f(P), rand, true)) { + kg->opgl_volume_sampling_distribution->ApplySingleLobeHenyeyGreensteinProduct(guiding_vec3f(D), + g); + return true; + } +#endif + + return false; +} + +ccl_device_forceinline float guiding_phase_sample(KernelGlobals kg, + IntegratorState state, + const float2 rand_phase, + ccl_private float3 *omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + pgl_vec3f wo; + const pgl_point2f rand = openpgl::cpp::Point2(rand_phase.x, rand_phase.y); + const float pdf = kg->opgl_volume_sampling_distribution->SamplePDF(rand, wo); + *omega_in = make_float3(wo.x, wo.y, wo.z); + return pdf; +#else + return 0.0f; +#endif +} + +ccl_device_forceinline float guiding_phase_pdf(KernelGlobals kg, + IntegratorState state, + const float3 omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + return kg->opgl_volume_sampling_distribution->PDF(guiding_vec3f(omega_in)); +#else + return 0.0f; +#endif +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/intersect_closest.h b/intern/cycles/kernel/integrator/intersect_closest.h index c7c3d74fa21..b9a81e25bcc 100644 --- a/intern/cycles/kernel/integrator/intersect_closest.h +++ b/intern/cycles/kernel/integrator/intersect_closest.h @@ -7,6 +7,7 @@ #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/shadow_catcher.h" @@ -48,13 +49,15 @@ ccl_device_forceinline bool integrator_intersect_terminate(KernelGlobals kg, * surfaces in front of emission do we need to evaluate the shader, since we * perform MIS as part of indirect rays. */ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - const float probability = path_state_continuation_probability(kg, state, path_flag); - INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = probability; + const float continuation_probability = path_state_continuation_probability(kg, state, path_flag); + INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = continuation_probability; - if (probability != 1.0f) { + guiding_record_continuation_probability(kg, state, continuation_probability); + + if (continuation_probability != 1.0f) { const float terminate = path_state_rng_1D(kg, &rng_state, PRNG_TERMINATE); - if (probability == 0.0f || terminate >= probability) { + if (continuation_probability == 0.0f || terminate >= continuation_probability) { if (shader_flags & SD_HAS_EMISSION) { /* Mark path to be terminated right after shader evaluation on the surface. */ INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE; diff --git a/intern/cycles/kernel/integrator/mnee.h b/intern/cycles/kernel/integrator/mnee.h index a39209bc937..038f0379bbc 100644 --- a/intern/cycles/kernel/integrator/mnee.h +++ b/intern/cycles/kernel/integrator/mnee.h @@ -807,7 +807,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg, float3 wo = normalize_len(vertices[0].p - sd->P, &wo_len); /* Initialize throughput and evaluate receiver bsdf * |n.wo|. */ - surface_shader_bsdf_eval(kg, sd, wo, throughput, ls->shader); + surface_shader_bsdf_eval(kg, state, sd, wo, throughput, ls->shader); /* Update light sample with new position / direct.ion * and keep pdf in vertex area measure */ diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 54560905397..dbc6fc5a883 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -56,6 +56,11 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = 1.0f; INTEGRATOR_STATE_WRITE(state, path, throughput) = one_spectrum(); +#ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) = 1.0f; + INTEGRATOR_STATE_WRITE(state, guiding, path_segment) = nullptr; +#endif + #ifdef __MNEE__ INTEGRATOR_STATE_WRITE(state, path, mnee) = 0; #endif @@ -249,7 +254,11 @@ ccl_device_inline float path_state_continuation_probability(KernelGlobals kg, /* Probabilistic termination: use sqrt() to roughly match typical view * transform and do path termination a bit later on average. */ - return min(sqrtf(reduce_max(fabs(INTEGRATOR_STATE(state, path, throughput)))), 1.0f); + Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + throughput *= INTEGRATOR_STATE(state, path, unguided_throughput); +#endif + return min(sqrtf(reduce_max(fabs(throughput))), 1.0f); } ccl_device_inline bool path_state_ao_bounce(KernelGlobals kg, ConstIntegratorState state) diff --git a/intern/cycles/kernel/integrator/shade_background.h b/intern/cycles/kernel/integrator/shade_background.h index 30ce0999258..8fc5689683a 100644 --- a/intern/cycles/kernel/integrator/shade_background.h +++ b/intern/cycles/kernel/integrator/shade_background.h @@ -5,6 +5,7 @@ #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/surface_shader.h" #include "kernel/light/light.h" @@ -124,6 +125,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg, mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf); } + guiding_record_background(kg, state, L, mis_weight); L *= mis_weight; } @@ -185,6 +187,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg, } /* Write to render buffer. */ + guiding_record_background(kg, state, light_eval, mis_weight); film_write_surface_emission( kg, state, light_eval, mis_weight, 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 f2d65eddfbb..e0b0500dc78 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -18,6 +18,8 @@ ccl_device_inline void integrate_light(KernelGlobals kg, Intersection isect ccl_optional_struct_init; integrator_state_read_isect(kg, state, &isect); + guiding_record_light_surface_segment(kg, state, &isect); + float3 ray_P = INTEGRATOR_STATE(state, ray, P); const float3 ray_D = INTEGRATOR_STATE(state, ray, D); const float ray_time = INTEGRATOR_STATE(state, ray, time); @@ -66,6 +68,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg, } /* Write to render buffer. */ + guiding_record_surface_emission(kg, state, light_eval, mis_weight); film_write_surface_emission(kg, state, light_eval, mis_weight, render_buffer, ls.group); } diff --git a/intern/cycles/kernel/integrator/shade_shadow.h b/intern/cycles/kernel/integrator/shade_shadow.h index ba18aed6ff0..bedb15ddf89 100644 --- a/intern/cycles/kernel/integrator/shade_shadow.h +++ b/intern/cycles/kernel/integrator/shade_shadow.h @@ -3,6 +3,7 @@ #pragma once +#include "kernel/integrator/guiding.h" #include "kernel/integrator/shade_volume.h" #include "kernel/integrator/surface_shader.h" #include "kernel/integrator/volume_stack.h" @@ -165,6 +166,7 @@ ccl_device void integrator_shade_shadow(KernelGlobals kg, return; } else { + guiding_record_direct_light(kg, state); film_write_direct_light(kg, state, render_buffer); integrator_shadow_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index c766ea2a07e..067d35ef9e3 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -9,6 +9,7 @@ #include "kernel/integrator/mnee.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/subsurface.h" #include "kernel/integrator/surface_shader.h" @@ -101,7 +102,7 @@ ccl_device_forceinline bool integrate_surface_holdout(KernelGlobals kg, } ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, - ConstIntegratorState state, + IntegratorState state, ccl_private const ShaderData *sd, ccl_global float *ccl_restrict render_buffer) @@ -128,6 +129,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf); } + guiding_record_surface_emission(kg, state, L, mis_weight); film_write_surface_emission( kg, state, L, mis_weight, render_buffer, object_lightgroup(kg, sd->object)); } @@ -210,7 +212,7 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, } /* Evaluate BSDF. */ - const float bsdf_pdf = surface_shader_bsdf_eval(kg, sd, ls.D, &bsdf_eval, ls.shader); + const float bsdf_pdf = surface_shader_bsdf_eval(kg, state, sd, ls.D, &bsdf_eval, ls.shader); bsdf_eval_mul(&bsdf_eval, light_eval / ls.pdf); if (ls.shader & SHADER_USE_MIS) { @@ -258,8 +260,8 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, /* Copy state from main path to shadow path. */ uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag); shadow_flag |= (is_light) ? PATH_RAY_SHADOW_FOR_LIGHT : 0; - const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput) * - bsdf_eval_sum(&bsdf_eval); + const Spectrum unlit_throughput = INTEGRATOR_STATE(state, path, throughput); + const Spectrum throughput = unlit_throughput * bsdf_eval_sum(&bsdf_eval); if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { PackedSpectrum pass_diffuse_weight; @@ -329,6 +331,11 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ? ls.group + 1 : kernel_data.background.lightgroup + 1; +#ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE( + state, guiding, path_segment); +#endif } /* Path tracing: bounce off or through surface with new direction. */ @@ -354,7 +361,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( #endif /* BSDF closure, sample direction. */ - float bsdf_pdf; + float bsdf_pdf = 0.0f, unguided_bsdf_pdf = 0.0f; BsdfEval bsdf_eval ccl_optional_struct_init; float3 bsdf_omega_in ccl_optional_struct_init; int label; @@ -362,18 +369,44 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( float2 bsdf_sampled_roughness = make_float2(1.0f, 1.0f); float bsdf_eta = 1.0f; - label = surface_shader_bsdf_sample_closure(kg, - sd, - sc, - rand_bsdf, - &bsdf_eval, - &bsdf_omega_in, - &bsdf_pdf, - &bsdf_sampled_roughness, - &bsdf_eta); +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kernel_data.integrator.use_surface_guiding) { + label = surface_shader_bsdf_guided_sample_closure(kg, + state, + sd, + sc, + rand_bsdf, + &bsdf_eval, + &bsdf_omega_in, + &bsdf_pdf, + &unguided_bsdf_pdf, + &bsdf_sampled_roughness, + &bsdf_eta); - if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { - return LABEL_NONE; + if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { + return LABEL_NONE; + } + + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= bsdf_pdf / unguided_bsdf_pdf; + } + else +#endif + { + label = surface_shader_bsdf_sample_closure(kg, + sd, + sc, + rand_bsdf, + &bsdf_eval, + &bsdf_omega_in, + &bsdf_pdf, + &bsdf_sampled_roughness, + &bsdf_eta); + + if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { + return LABEL_NONE; + } + + unguided_bsdf_pdf = bsdf_pdf; } if (label & LABEL_TRANSPARENT) { @@ -393,9 +426,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( } /* Update throughput. */ - Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); - throughput *= bsdf_eval_sum(&bsdf_eval) / bsdf_pdf; - INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput; + const Spectrum bsdf_weight = bsdf_eval_sum(&bsdf_eval) / bsdf_pdf; + INTEGRATOR_STATE_WRITE(state, path, throughput) *= bsdf_weight; if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { if (INTEGRATOR_STATE(state, path, bounce) == 0) { @@ -410,10 +442,21 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( if (!(label & LABEL_TRANSPARENT)) { INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( - bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); + unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); } path_state_next(kg, state, label); + + guiding_record_surface_bounce(kg, + state, + sd, + bsdf_weight, + bsdf_pdf, + sd->N, + normalize(bsdf_omega_in), + bsdf_sampled_roughness, + bsdf_eta); + return label; } @@ -435,14 +478,15 @@ ccl_device_forceinline int integrate_surface_volume_only_bounce(IntegratorState ccl_device_forceinline bool integrate_surface_terminate(IntegratorState state, const uint32_t path_flag) { - const float probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? - 0.0f : - INTEGRATOR_STATE(state, path, continuation_probability); - if (probability == 0.0f) { + const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? + 0.0f : + INTEGRATOR_STATE( + state, path, continuation_probability); + if (continuation_probability == 0.0f) { return true; } - else if (probability != 1.0f) { - INTEGRATOR_STATE_WRITE(state, path, throughput) /= probability; + else if (continuation_probability != 1.0f) { + INTEGRATOR_STATE_WRITE(state, path, throughput) /= continuation_probability; } return false; @@ -550,6 +594,8 @@ ccl_device bool integrate_surface(KernelGlobals kg, #ifdef __VOLUME__ if (!(sd.flag & SD_HAS_ONLY_VOLUME)) { #endif + guiding_record_surface_segment(kg, state, &sd); + #ifdef __SUBSURFACE__ /* Can skip shader evaluation for BSSRDF exit point without bump mapping. */ if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP))) @@ -615,6 +661,10 @@ ccl_device bool integrate_surface(KernelGlobals kg, RNGState rng_state; path_state_rng_load(state, &rng_state); +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + surface_shader_prepare_guiding(kg, state, &sd, &rng_state); + guiding_write_debug_passes(kg, state, &sd, render_buffer); +#endif /* Direct light. */ PROFILING_EVENT(PROFILING_SHADE_SURFACE_DIRECT_LIGHT); integrate_surface_direct_light(kg, state, &sd, &rng_state); diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index aaef92729d6..a8324cda2dc 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -7,6 +7,7 @@ #include "kernel/film/denoising_passes.h" #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/intersect_closest.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/volume_shader.h" @@ -612,6 +613,7 @@ ccl_device_forceinline void volume_integrate_heterogeneous( const Spectrum emission = volume_emission_integrate( &coeff, closure_flag, transmittance, dt); accum_emission += result.indirect_throughput * emission; + guiding_record_volume_emission(kg, state, emission); } } @@ -761,7 +763,7 @@ ccl_device_forceinline void integrate_volume_direct_light( /* Evaluate BSDF. */ BsdfEval phase_eval ccl_optional_struct_init; - const float phase_pdf = volume_shader_phase_eval(kg, sd, phases, ls->D, &phase_eval); + float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval); if (ls->shader & SHADER_USE_MIS) { float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf); @@ -848,6 +850,12 @@ ccl_device_forceinline void integrate_volume_direct_light( ls->group + 1 : kernel_data.background.lightgroup + 1; +# ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE( + state, guiding, path_segment); +# endif + integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state); } @@ -861,18 +869,54 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( { PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_INDIRECT_LIGHT); - const float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE); + float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE); + + ccl_private const ShaderVolumeClosure *svc = volume_shader_phase_pick(phases, &rand_phase); /* Phase closure, sample direction. */ - float phase_pdf; + float phase_pdf = 0.0f, unguided_phase_pdf = 0.0f; BsdfEval phase_eval ccl_optional_struct_init; float3 phase_omega_in ccl_optional_struct_init; + float sampled_roughness = 1.0f; + int label; - const int label = volume_shader_phase_sample( - kg, sd, phases, rand_phase, &phase_eval, &phase_omega_in, &phase_pdf); +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kernel_data.integrator.use_guiding) { + label = volume_shader_phase_guided_sample(kg, + state, + sd, + svc, + rand_phase, + &phase_eval, + &phase_omega_in, + &phase_pdf, + &unguided_phase_pdf, + &sampled_roughness); - if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { - return false; + if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { + return false; + } + + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= phase_pdf / unguided_phase_pdf; + } + else +# endif + { + label = volume_shader_phase_sample(kg, + sd, + phases, + svc, + rand_phase, + &phase_eval, + &phase_omega_in, + &phase_pdf, + &sampled_roughness); + + if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { + return false; + } + + unguided_phase_pdf = phase_pdf; } /* Setup ray. */ @@ -887,9 +931,15 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( INTEGRATOR_STATE_WRITE(state, isect, prim) = sd->prim; INTEGRATOR_STATE_WRITE(state, isect, object) = sd->object; + const Spectrum phase_weight = bsdf_eval_sum(&phase_eval) / phase_pdf; + + /* Add phase function sampling data to the path segment. */ + guiding_record_volume_bounce( + kg, state, sd, phase_weight, phase_pdf, normalize(phase_omega_in), sampled_roughness); + /* Update throughput. */ const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); - const Spectrum throughput_phase = throughput * bsdf_eval_sum(&phase_eval) / phase_pdf; + const Spectrum throughput_phase = throughput * phase_weight; INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput_phase; if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { @@ -900,7 +950,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( /* Update path state */ INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( - phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); + unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); path_state_next(kg, state, label); return true; @@ -939,6 +989,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, VOLUME_READ_LAMBDA(integrator_state_read_volume_stack(state, i)) const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass); +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput); +# endif + /* TODO: expensive to zero closures? */ VolumeIntegrateResult result = {}; volume_integrate_heterogeneous(kg, @@ -956,17 +1010,50 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, * to be terminated. That will shading evaluating to leave out any scattering closures, * but emission and absorption are still handled for multiple importance sampling. */ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - const float probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ? - 0.0f : - INTEGRATOR_STATE(state, path, continuation_probability); - if (probability == 0.0f) { + const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ? + 0.0f : + INTEGRATOR_STATE( + state, path, continuation_probability); + if (continuation_probability == 0.0f) { return VOLUME_PATH_MISSED; } +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + bool guiding_generated_new_segment = false; + if (kernel_data.integrator.use_guiding) { + /* Record transmittance using change in throughput. */ + float3 transmittance_weight = spectrum_to_rgb( + safe_divide_color(result.indirect_throughput, initial_throughput)); + guiding_record_volume_transmission(kg, state, transmittance_weight); + + if (result.indirect_scatter) { + const float3 P = ray->P + result.indirect_t * ray->D; + + /* Record volume segment up to direct scatter position. + * TODO: volume segment is wrong when direct_t and indirect_t. */ + if (result.direct_scatter && (result.direct_t == result.indirect_t)) { + guiding_record_volume_segment(kg, state, P, sd.I); + guiding_generated_new_segment = true; + } + +# if PATH_GUIDING_LEVEL >= 4 + /* TODO: this position will be wrong for direct light pdf computation, + * since the direct light position may be different? */ + volume_shader_prepare_guiding( + kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method); +# endif + } + else { + /* No guiding if we don't scatter. */ + state->guiding.use_volume_guiding = false; + } + } +# endif + /* Direct light. */ if (result.direct_scatter) { const float3 direct_P = ray->P + result.direct_t * ray->D; - result.direct_throughput /= probability; + result.direct_throughput /= continuation_probability; integrate_volume_direct_light(kg, state, &sd, @@ -979,16 +1066,22 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, /* Indirect light. * - * Only divide throughput by probability if we scatter. For the attenuation + * Only divide throughput by continuation_probability if we scatter. For the attenuation * case the next surface will already do this division. */ if (result.indirect_scatter) { - result.indirect_throughput /= probability; + result.indirect_throughput /= continuation_probability; } INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput; if (result.indirect_scatter) { sd.P = ray->P + result.indirect_t * ray->D; +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!guiding_generated_new_segment) { + guiding_record_volume_segment(kg, state, sd.P, sd.I); + } +# endif + if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) { return VOLUME_PATH_SCATTERED; } diff --git a/intern/cycles/kernel/integrator/shadow_state_template.h b/intern/cycles/kernel/integrator/shadow_state_template.h index 3b490ecffdd..d731d1df339 100644 --- a/intern/cycles/kernel/integrator/shadow_state_template.h +++ b/intern/cycles/kernel/integrator/shadow_state_template.h @@ -40,6 +40,16 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEA 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) +/* Path guiding. */ +KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, unlit_throughput, KERNEL_FEATURE_PATH_GUIDING) +#ifdef __PATH_GUIDING__ +KERNEL_STRUCT_MEMBER(shadow_path, + openpgl::cpp::PathSegment *, + path_segment, + KERNEL_FEATURE_PATH_GUIDING) +#else +KERNEL_STRUCT_MEMBER(shadow_path, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING) +#endif KERNEL_STRUCT_END(shadow_path) /********************************** Shadow Ray *******************************/ diff --git a/intern/cycles/kernel/integrator/state.h b/intern/cycles/kernel/integrator/state.h index d1907bd6e16..f0fdc6f0d54 100644 --- a/intern/cycles/kernel/integrator/state.h +++ b/intern/cycles/kernel/integrator/state.h @@ -31,6 +31,10 @@ #include "util/types.h" +#ifdef __PATH_GUIDING__ +# include "util/guiding.h" +#endif + #pragma once CCL_NAMESPACE_BEGIN diff --git a/intern/cycles/kernel/integrator/state_template.h b/intern/cycles/kernel/integrator/state_template.h index f4e280e4cb2..760c2f80521 100644 --- a/intern/cycles/kernel/integrator/state_template.h +++ b/intern/cycles/kernel/integrator/state_template.h @@ -47,6 +47,9 @@ KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(path, float, continuation_probability, KERNEL_FEATURE_PATH_TRACING) /* Throughput. */ KERNEL_STRUCT_MEMBER(path, PackedSpectrum, throughput, KERNEL_FEATURE_PATH_TRACING) +/* Factor to multiple with throughput to get remove any guiding pdfs. + * Such throughput without guiding pdfs is used for Russian rouletter termination. */ +KERNEL_STRUCT_MEMBER(path, float, unguided_throughput, KERNEL_FEATURE_PATH_GUIDING) /* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */ KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES) KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES) @@ -98,3 +101,33 @@ KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, shader, KERNEL_FEATURE_VOLUME) KERNEL_STRUCT_END_ARRAY(volume_stack, KERNEL_STRUCT_VOLUME_STACK_SIZE, KERNEL_STRUCT_VOLUME_STACK_SIZE) + +/************************************ Path Guiding *****************************/ +KERNEL_STRUCT_BEGIN(guiding) +#ifdef __PATH_GUIDING__ +/* Current path segment of the random walk/path. */ +KERNEL_STRUCT_MEMBER(guiding, + openpgl::cpp::PathSegment *, + path_segment, + KERNEL_FEATURE_PATH_GUIDING) +#else +/* Current path segment of the random walk/path. */ +KERNEL_STRUCT_MEMBER(guiding, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING) +#endif +/* If surface guiding is enabled */ +KERNEL_STRUCT_MEMBER(guiding, bool, use_surface_guiding, KERNEL_FEATURE_PATH_GUIDING) +/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding + * or bsdf sampling) */ +KERNEL_STRUCT_MEMBER(guiding, float, sample_surface_guiding_rand, KERNEL_FEATURE_PATH_GUIDING) +/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob)*/ +KERNEL_STRUCT_MEMBER(guiding, float, surface_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +/* Probability of sampling a bssrdf closure instead of a bsdf closure*/ +KERNEL_STRUCT_MEMBER(guiding, float, bssrdf_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +/* If volume guiding is enabled */ +KERNEL_STRUCT_MEMBER(guiding, bool, use_volume_guiding, KERNEL_FEATURE_PATH_GUIDING) +/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding + * or bsdf sampling) */ +KERNEL_STRUCT_MEMBER(guiding, float, sample_volume_guiding_rand, KERNEL_FEATURE_PATH_GUIDING) +/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob)*/ +KERNEL_STRUCT_MEMBER(guiding, float, volume_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +KERNEL_STRUCT_END(guiding) diff --git a/intern/cycles/kernel/integrator/subsurface.h b/intern/cycles/kernel/integrator/subsurface.h index 15c2cb1c708..efd293e4141 100644 --- a/intern/cycles/kernel/integrator/subsurface.h +++ b/intern/cycles/kernel/integrator/subsurface.h @@ -78,6 +78,9 @@ ccl_device int subsurface_bounce(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, subsurface, radius) = bssrdf->radius; INTEGRATOR_STATE_WRITE(state, subsurface, anisotropy) = bssrdf->anisotropy; + /* Path guiding. */ + guiding_record_bssrdf_weight(kg, state, weight, bssrdf->albedo); + return LABEL_SUBSURFACE_SCATTER; } diff --git a/intern/cycles/kernel/integrator/subsurface_disk.h b/intern/cycles/kernel/integrator/subsurface_disk.h index a44b6a74d7b..16fb45392f4 100644 --- a/intern/cycles/kernel/integrator/subsurface_disk.h +++ b/intern/cycles/kernel/integrator/subsurface_disk.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ +#include "kernel/integrator/guiding.h" + CCL_NAMESPACE_BEGIN /* BSSRDF using disk based importance sampling. @@ -173,8 +175,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, if (r < next_sum) { /* Return exit point. */ - INTEGRATOR_STATE_WRITE(state, path, throughput) *= weight * sum_weights / sample_weight; - + const Spectrum resampled_weight = weight * sum_weights / sample_weight; + INTEGRATOR_STATE_WRITE(state, path, throughput) *= resampled_weight; ss_isect.hits[0] = ss_isect.hits[hit]; ss_isect.Ng[0] = ss_isect.Ng[hit]; @@ -182,6 +184,9 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, ray.D = ss_isect.Ng[hit]; ray.tmin = 0.0f; ray.tmax = 1.0f; + + guiding_record_bssrdf_bounce( + kg, state, 1.0f, Ng, -Ng, resampled_weight, INTEGRATOR_STATE(state, subsurface, albedo)); return true; } diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index a6a59e286c9..fdcb66c32f5 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -5,6 +5,8 @@ #include "kernel/bvh/bvh.h" +#include "kernel/integrator/guiding.h" + CCL_NAMESPACE_BEGIN /* Random walk subsurface scattering. @@ -203,7 +205,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, const float anisotropy = INTEGRATOR_STATE(state, subsurface, anisotropy); Spectrum sigma_t, alpha; - Spectrum throughput = INTEGRATOR_STATE_WRITE(state, path, throughput); + Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); subsurface_random_walk_coefficients(albedo, radius, anisotropy, &sigma_t, &alpha, &throughput); Spectrum sigma_s = sigma_t * alpha; @@ -350,7 +352,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, } } - /* Sample direction along ray. */ + /* Sample distance along ray. */ float t = -logf(1.0f - randt) / sample_sigma_t; /* On the first bounce, we use the ray-cast to check if the opposite side is nearby. @@ -432,6 +434,16 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, if (hit) { kernel_assert(isfinite_safe(throughput)); + + guiding_record_bssrdf_bounce( + kg, + state, + pdf, + N, + D, + safe_divide_color(throughput, INTEGRATOR_STATE(state, path, throughput)), + albedo); + INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput; } diff --git a/intern/cycles/kernel/integrator/surface_shader.h b/intern/cycles/kernel/integrator/surface_shader.h index 7da81984a12..6c0097b11bd 100644 --- a/intern/cycles/kernel/integrator/surface_shader.h +++ b/intern/cycles/kernel/integrator/surface_shader.h @@ -10,6 +10,8 @@ #include "kernel/closure/bsdf_util.h" #include "kernel/closure/emissive.h" +#include "kernel/integrator/guiding.h" + #ifdef __SVM__ # include "kernel/svm/svm.h" #endif @@ -19,6 +21,67 @@ CCL_NAMESPACE_BEGIN +/* Guiding */ + +#ifdef __PATH_GUIDING__ +ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const RNGState *rng_state) +{ + /* Have any BSDF to guide? */ + if (!(kernel_data.integrator.use_surface_guiding && (sd->flag & SD_BSDF_HAS_EVAL))) { + state->guiding.use_surface_guiding = false; + return; + } + + const float surface_guiding_probability = kernel_data.integrator.surface_guiding_probability; + float rand_bsdf_guiding = path_state_rng_1D(kg, rng_state, PRNG_SURFACE_BSDF_GUIDING); + + /* Compute proportion of diffuse BSDF and BSSRDFs .*/ + float diffuse_sampling_fraction = 0.0f; + float bssrdf_sampling_fraction = 0.0f; + float bsdf_bssrdf_sampling_sum = 0.0f; + + for (int i = 0; i < sd->num_closure; i++) { + ShaderClosure *sc = &sd->closure[i]; + if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + const float sweight = sc->sample_weight; + kernel_assert(sweight >= 0.0f); + + bsdf_bssrdf_sampling_sum += sweight; + if (CLOSURE_IS_BSDF_DIFFUSE(sc->type) && sc->type < CLOSURE_BSDF_TRANSLUCENT_ID) { + diffuse_sampling_fraction += sweight; + } + if (CLOSURE_IS_BSSRDF(sc->type)) { + bssrdf_sampling_fraction += sweight; + } + } + } + + if (bsdf_bssrdf_sampling_sum > 0.0f) { + diffuse_sampling_fraction /= bsdf_bssrdf_sampling_sum; + bssrdf_sampling_fraction /= bsdf_bssrdf_sampling_sum; + } + + /* Init guiding (diffuse BSDFs only for now). */ + if (!(diffuse_sampling_fraction > 0.0f && + guiding_bsdf_init(kg, state, sd->P, sd->N, rand_bsdf_guiding))) { + state->guiding.use_surface_guiding = false; + return; + } + + state->guiding.use_surface_guiding = true; + state->guiding.surface_guiding_sampling_prob = surface_guiding_probability * + diffuse_sampling_fraction; + state->guiding.bssrdf_sampling_prob = bssrdf_sampling_fraction; + state->guiding.sample_surface_guiding_rand = rand_bsdf_guiding; + + kernel_assert(state->guiding.surface_guiding_sampling_prob > 0.0f && + state->guiding.surface_guiding_sampling_prob <= 1.0f); +} +#endif + ccl_device_inline void surface_shader_prepare_closures(KernelGlobals kg, ConstIntegratorState state, ccl_private ShaderData *sd, @@ -108,6 +171,28 @@ ccl_device_inline void surface_shader_prepare_closures(KernelGlobals kg, } /* BSDF */ +#if 0 +ccl_device_inline void surface_shader_validate_bsdf_sample(const KernelGlobals kg, + const ShaderClosure *sc, + const float3 omega_in, + const int org_label, + const float2 org_roughness, + const float org_eta) +{ + /* Validate the the bsdf_label and bsdf_roughness_eta functions + * by estimating the values after a bsdf sample. */ + const int comp_label = bsdf_label(kg, sc, omega_in); + kernel_assert(org_label == comp_label); + + float2 comp_roughness; + float comp_eta; + bsdf_roughness_eta(kg, sc, &comp_roughness, &comp_eta); + kernel_assert(org_eta == comp_eta); + kernel_assert(org_roughness.x == comp_roughness.x); + kernel_assert(org_roughness.y == comp_roughness.y); +} +#endif + ccl_device_forceinline bool _surface_shader_exclude(ClosureType type, uint light_shader_flags) { if (!(light_shader_flags & SHADER_EXCLUDE_ANY)) { @@ -167,6 +252,55 @@ ccl_device_inline float _surface_shader_bsdf_eval_mis(KernelGlobals kg, return (sum_sample_weight > 0.0f) ? sum_pdf / sum_sample_weight : 0.0f; } +ccl_device_inline float surface_shader_bsdf_eval_pdfs(const KernelGlobals kg, + ccl_private ShaderData *sd, + const float3 omega_in, + ccl_private BsdfEval *result_eval, + ccl_private float *pdfs, + const uint light_shader_flags) +{ + /* This is the veach one-sample model with balance heuristic, some pdf + * factors drop out when using balance heuristic weighting. */ + float sum_pdf = 0.0f; + float sum_sample_weight = 0.0f; + bsdf_eval_init(result_eval, CLOSURE_NONE_ID, zero_spectrum()); + for (int i = 0; i < sd->num_closure; i++) { + ccl_private const ShaderClosure *sc = &sd->closure[i]; + + if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + if (CLOSURE_IS_BSDF(sc->type) && !_surface_shader_exclude(sc->type, light_shader_flags)) { + float bsdf_pdf = 0.0f; + Spectrum eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf); + kernel_assert(bsdf_pdf >= 0.0f); + if (bsdf_pdf != 0.0f) { + bsdf_eval_accum(result_eval, sc->type, eval * sc->weight); + sum_pdf += bsdf_pdf * sc->sample_weight; + kernel_assert(bsdf_pdf * sc->sample_weight >= 0.0f); + pdfs[i] = bsdf_pdf * sc->sample_weight; + } + else { + pdfs[i] = 0.0f; + } + } + else { + pdfs[i] = 0.0f; + } + + sum_sample_weight += sc->sample_weight; + } + else { + pdfs[i] = 0.0f; + } + } + if (sum_pdf > 0.0f) { + for (int i = 0; i < sd->num_closure; i++) { + pdfs[i] /= sum_pdf; + } + } + + return (sum_sample_weight > 0.0f) ? sum_pdf / sum_sample_weight : 0.0f; +} + #ifndef __KERNEL_CUDA__ ccl_device #else @@ -174,6 +308,7 @@ ccl_device_inline #endif float surface_shader_bsdf_eval(KernelGlobals kg, + IntegratorState state, ccl_private ShaderData *sd, const float3 omega_in, ccl_private BsdfEval *bsdf_eval, @@ -181,8 +316,20 @@ ccl_device_inline { bsdf_eval_init(bsdf_eval, CLOSURE_NONE_ID, zero_spectrum()); - return _surface_shader_bsdf_eval_mis( + float pdf = _surface_shader_bsdf_eval_mis( kg, sd, omega_in, NULL, bsdf_eval, 0.0f, 0.0f, light_shader_flags); + +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (state->guiding.use_surface_guiding) { + const float guiding_sampling_prob = state->guiding.surface_guiding_sampling_prob; + const float bssrdf_sampling_prob = state->guiding.bssrdf_sampling_prob; + const float guide_pdf = guiding_bsdf_pdf(kg, state, omega_in); + pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) + + (1.0f - guiding_sampling_prob) * pdf; + } +#endif + + return pdf; } /* Randomly sample a BSSRDF or BSDF proportional to ShaderClosure.sample_weight. */ @@ -250,6 +397,135 @@ surface_shader_bssrdf_sample_weight(ccl_private const ShaderData *ccl_restrict s return weight; } +#ifdef __PATH_GUIDING__ +/* Sample direction for picked BSDF, and return evaluation and pdf for all + * BSDFs combined using MIS. */ + +ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const ShaderClosure *sc, + const float2 rand_bsdf, + ccl_private BsdfEval *bsdf_eval, + ccl_private float3 *omega_in, + ccl_private float *bsdf_pdf, + ccl_private float *unguided_bsdf_pdf, + ccl_private float2 *sampled_rougness, + ccl_private float *eta) +{ + /* BSSRDF should already have been handled elsewhere. */ + kernel_assert(CLOSURE_IS_BSDF(sc->type)); + + const bool use_surface_guiding = state->guiding.use_surface_guiding; + const float guiding_sampling_prob = state->guiding.surface_guiding_sampling_prob; + const float bssrdf_sampling_prob = state->guiding.bssrdf_sampling_prob; + + /* Decide between sampling guiding distribution and BSDF. */ + bool sample_guiding = false; + float rand_bsdf_guiding = state->guiding.sample_surface_guiding_rand; + + if (use_surface_guiding && rand_bsdf_guiding < guiding_sampling_prob) { + sample_guiding = true; + rand_bsdf_guiding /= guiding_sampling_prob; + } + else { + rand_bsdf_guiding -= guiding_sampling_prob; + rand_bsdf_guiding /= (1.0f - guiding_sampling_prob); + } + + /* Initialize to zero. */ + int label = LABEL_NONE; + Spectrum eval = zero_spectrum(); + bsdf_eval_init(bsdf_eval, CLOSURE_NONE_ID, eval); + + *unguided_bsdf_pdf = 0.0f; + float guide_pdf = 0.0f; + + if (sample_guiding) { + /* Sample guiding distribution. */ + guide_pdf = guiding_bsdf_sample(kg, state, rand_bsdf, omega_in); + *bsdf_pdf = 0.0f; + + if (guide_pdf != 0.0f) { + float unguided_bsdf_pdfs[MAX_CLOSURE]; + + *unguided_bsdf_pdf = surface_shader_bsdf_eval_pdfs( + kg, sd, *omega_in, bsdf_eval, unguided_bsdf_pdfs, 0); + *bsdf_pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) + + ((1.0f - guiding_sampling_prob) * (*unguided_bsdf_pdf)); + float sum_pdfs = 0.0f; + + if (*unguided_bsdf_pdf > 0.0f) { + int idx = -1; + for (int i = 0; i < sd->num_closure; i++) { + sum_pdfs += unguided_bsdf_pdfs[i]; + if (rand_bsdf_guiding <= sum_pdfs) { + idx = i; + break; + } + } + + kernel_assert(idx >= 0); + /* Set the default idx to the last in the list. + * in case of numerical problems and rand_bsdf_guiding is just >=1.0f and + * the sum of all unguided_bsdf_pdfs is just < 1.0f. */ + idx = (rand_bsdf_guiding > sum_pdfs) ? sd->num_closure - 1 : idx; + + label = bsdf_label(kg, &sd->closure[idx], *omega_in); + } + } + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + + *sampled_rougness = make_float2(1.0f, 1.0f); + *eta = 1.0f; + } + else { + /* Sample BSDF. */ + *bsdf_pdf = 0.0f; + label = bsdf_sample(kg, + sd, + sc, + rand_bsdf.x, + rand_bsdf.y, + &eval, + omega_in, + unguided_bsdf_pdf, + sampled_rougness, + eta); +# if 0 + if (*unguided_bsdf_pdf > 0.0f) { + surface_shader_validate_bsdf_sample(kg, sc, *omega_in, label, sampled_roughness, eta); + } +# endif + + if (*unguided_bsdf_pdf != 0.0f) { + bsdf_eval_init(bsdf_eval, sc->type, eval * sc->weight); + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + + if (sd->num_closure > 1) { + float sweight = sc->sample_weight; + *unguided_bsdf_pdf = _surface_shader_bsdf_eval_mis( + kg, sd, *omega_in, sc, bsdf_eval, (*unguided_bsdf_pdf) * sweight, sweight, 0); + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + } + *bsdf_pdf = *unguided_bsdf_pdf; + + if (use_surface_guiding) { + guide_pdf = guiding_bsdf_pdf(kg, state, *omega_in); + *bsdf_pdf *= 1.0f - guiding_sampling_prob; + *bsdf_pdf += guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob); + } + } + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + } + + return label; +} +#endif + /* Sample direction for picked BSDF, and return evaluation and pdf for all * BSDFs combined using MIS. */ ccl_device int surface_shader_bsdf_sample_closure(KernelGlobals kg, @@ -281,6 +557,9 @@ ccl_device int surface_shader_bsdf_sample_closure(KernelGlobals kg, kg, sd, *omega_in, sc, bsdf_eval, *pdf * sweight, sweight, 0); } } + else { + bsdf_eval_init(bsdf_eval, sc->type, zero_spectrum()); + } return label; } diff --git a/intern/cycles/kernel/integrator/volume_shader.h b/intern/cycles/kernel/integrator/volume_shader.h index 31039bfdcf5..d3cfa58db96 100644 --- a/intern/cycles/kernel/integrator/volume_shader.h +++ b/intern/cycles/kernel/integrator/volume_shader.h @@ -22,6 +22,7 @@ CCL_NAMESPACE_BEGIN #ifdef __VOLUME__ /* Merging */ + ccl_device_inline void volume_shader_merge_closures(ccl_private ShaderData *sd) { /* Merge identical closures to save closure space with stacked volumes. */ @@ -88,6 +89,119 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases } } +/* Guiding */ + +# ifdef __PATH_GUIDING__ +ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const RNGState *rng_state, + const float3 P, + const float3 D, + ccl_private ShaderVolumePhases *phases, + const VolumeSampleMethod direct_sample_method) +{ + /* Have any phase functions to guide? */ + const int num_phases = phases->num_closure; + if (!kernel_data.integrator.use_volume_guiding || num_phases == 0) { + state->guiding.use_volume_guiding = false; + return; + } + + const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability; + float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING); + + /* If we have more than one ohase function we select one random based on its + * sample weight to caclulate the product distribution for guiding. */ + int phase_id = 0; + float phase_weight = 1.0f; + + if (num_phases > 1) { + /* Pick a phase closure based on sample weights. */ + float sum = 0.0f; + + for (phase_id = 0; phase_id < num_phases; phase_id++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + sum += svc->sample_weight; + } + + float r = rand_phase_guiding * sum; + float partial_sum = 0.0f; + + for (phase_id = 0; phase_id < num_phases; phase_id++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + float next_sum = partial_sum + svc->sample_weight; + + if (r <= next_sum) { + /* Rescale to reuse. */ + rand_phase_guiding = (r - partial_sum) / svc->sample_weight; + phase_weight = svc->sample_weight / sum; + break; + } + + partial_sum = next_sum; + } + + /* Adjust the sample weight of the component used for guiding. */ + phases->closure[phase_id].sample_weight *= volume_guiding_probability; + } + + /* Init guiding for selected phase function. */ + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + if (!guiding_phase_init(kg, state, P, D, svc->g, rand_phase_guiding)) { + state->guiding.use_volume_guiding = false; + return; + } + + state->guiding.use_volume_guiding = true; + state->guiding.sample_volume_guiding_rand = rand_phase_guiding; + state->guiding.volume_guiding_sampling_prob = volume_guiding_probability * phase_weight; + + kernel_assert(state->guiding.volume_guiding_sampling_prob > 0.0f && + state->guiding.volume_guiding_sampling_prob <= 1.0f); +} +# endif + +/* Phase Evaluation & Sampling */ + +/* Randomly sample a volume phase function proportional to ShaderClosure.sample_weight. */ +ccl_device_inline ccl_private const ShaderVolumeClosure *volume_shader_phase_pick( + ccl_private const ShaderVolumePhases *phases, ccl_private float2 *rand_phase) +{ + int sampled = 0; + + if (phases->num_closure > 1) { + /* pick a phase closure based on sample weights */ + float sum = 0.0f; + + for (int i = 0; i < phases->num_closure; i++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; + sum += svc->sample_weight; + } + + float r = (*rand_phase).x * sum; + float partial_sum = 0.0f; + + for (int i = 0; i < phases->num_closure; i++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[i]; + float next_sum = partial_sum + svc->sample_weight; + + if (r <= next_sum) { + /* Rescale to reuse for volume phase direction sample. */ + sampled = i; + (*rand_phase).x = (r - partial_sum) / svc->sample_weight; + break; + } + + partial_sum = next_sum; + } + } + + /* todo: this isn't quite correct, we don't weight anisotropy properly + * depending on color channels, even if this is perhaps not a common case */ + return &phases->closure[sampled]; +} + ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderData *sd, ccl_private const ShaderVolumePhases *phases, const float3 omega_in, @@ -116,6 +230,23 @@ ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderDa } ccl_device float volume_shader_phase_eval(KernelGlobals kg, + ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + const float3 omega_in, + ccl_private BsdfEval *phase_eval) +{ + float phase_pdf = 0.0f; + Spectrum eval = volume_phase_eval(sd, svc, omega_in, &phase_pdf); + + if (phase_pdf != 0.0f) { + bsdf_eval_accum(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + } + + return phase_pdf; +} + +ccl_device float volume_shader_phase_eval(KernelGlobals kg, + IntegratorState state, ccl_private const ShaderData *sd, ccl_private const ShaderVolumePhases *phases, const float3 omega_in, @@ -123,82 +254,116 @@ ccl_device float volume_shader_phase_eval(KernelGlobals kg, { bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); - return _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f); + float pdf = _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f); + +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (state->guiding.use_volume_guiding) { + const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob; + const float guide_pdf = guiding_phase_pdf(kg, state, omega_in); + pdf = (guiding_sampling_prob * guide_pdf) + (1.0f - guiding_sampling_prob) * pdf; + } +# endif + + return pdf; } -ccl_device int volume_shader_phase_sample(KernelGlobals kg, - ccl_private const ShaderData *sd, - ccl_private const ShaderVolumePhases *phases, - float2 rand_phase, - ccl_private BsdfEval *phase_eval, - ccl_private float3 *omega_in, - ccl_private float *pdf) +# ifdef __PATH_GUIDING__ +ccl_device int volume_shader_phase_guided_sample(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + const float2 rand_phase, + ccl_private BsdfEval *phase_eval, + ccl_private float3 *omega_in, + ccl_private float *phase_pdf, + ccl_private float *unguided_phase_pdf, + ccl_private float *sampled_roughness) { - int sampled = 0; + const bool use_volume_guiding = state->guiding.use_volume_guiding; + const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob; - if (phases->num_closure > 1) { - /* pick a phase closure based on sample weights */ - float sum = 0.0f; - - for (sampled = 0; sampled < phases->num_closure; sampled++) { - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - sum += svc->sample_weight; - } - - float r = rand_phase.x * sum; - float partial_sum = 0.0f; - - for (sampled = 0; sampled < phases->num_closure; sampled++) { - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - float next_sum = partial_sum + svc->sample_weight; - - if (r <= next_sum) { - /* Rescale to reuse for BSDF direction sample. */ - rand_phase.x = (r - partial_sum) / svc->sample_weight; - break; - } - - partial_sum = next_sum; - } - - if (sampled == phases->num_closure) { - *pdf = 0.0f; - return LABEL_NONE; - } + /* Decide between sampling guiding distribution and phase. */ + float rand_phase_guiding = state->guiding.sample_volume_guiding_rand; + bool sample_guiding = false; + if (use_volume_guiding && rand_phase_guiding < guiding_sampling_prob) { + sample_guiding = true; + rand_phase_guiding /= guiding_sampling_prob; + } + else { + rand_phase_guiding -= guiding_sampling_prob; + rand_phase_guiding /= (1.0f - guiding_sampling_prob); } - /* todo: this isn't quite correct, we don't weight anisotropy properly - * depending on color channels, even if this is perhaps not a common case */ - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - int label; + /* Initialize to zero. */ + int label = LABEL_NONE; Spectrum eval = zero_spectrum(); - *pdf = 0.0f; - label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); + *unguided_phase_pdf = 0.0f; + float guide_pdf = 0.0f; + *sampled_roughness = 1.0f - fabsf(svc->g); - if (*pdf != 0.0f) { - bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); + + if (sample_guiding) { + /* Sample guiding distribution. */ + guide_pdf = guiding_phase_sample(kg, state, rand_phase, omega_in); + *phase_pdf = 0.0f; + + if (guide_pdf != 0.0f) { + *unguided_phase_pdf = volume_shader_phase_eval(kg, sd, svc, *omega_in, phase_eval); + *phase_pdf = (guiding_sampling_prob * guide_pdf) + + ((1.0f - guiding_sampling_prob) * (*unguided_phase_pdf)); + label = LABEL_VOLUME_SCATTER; + } + } + else { + /* Sample phase. */ + *phase_pdf = 0.0f; + label = volume_phase_sample( + sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, unguided_phase_pdf); + + if (*unguided_phase_pdf != 0.0f) { + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + + *phase_pdf = *unguided_phase_pdf; + if (use_volume_guiding) { + guide_pdf = guiding_phase_pdf(kg, state, *omega_in); + *phase_pdf *= 1.0f - guiding_sampling_prob; + *phase_pdf += guiding_sampling_prob * guide_pdf; + } + + kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f); + } + else { + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); + } + + kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f); } return label; } +# endif -ccl_device int volume_shader_phase_sample_closure(KernelGlobals kg, - ccl_private const ShaderData *sd, - ccl_private const ShaderVolumeClosure *sc, - const float2 rand_phase, - ccl_private BsdfEval *phase_eval, - ccl_private float3 *omega_in, - ccl_private float *pdf) +ccl_device int volume_shader_phase_sample(KernelGlobals kg, + ccl_private const ShaderData *sd, + ccl_private const ShaderVolumePhases *phases, + ccl_private const ShaderVolumeClosure *svc, + float2 rand_phase, + ccl_private BsdfEval *phase_eval, + ccl_private float3 *omega_in, + ccl_private float *pdf, + ccl_private float *sampled_roughness) { - int label; + *sampled_roughness = 1.0f - fabsf(svc->g); Spectrum eval = zero_spectrum(); *pdf = 0.0f; - label = volume_phase_sample(sd, sc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); + int label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); - if (*pdf != 0.0f) + if (*pdf != 0.0f) { bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + } return label; } diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index bd3791594e0..f81e7843629 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -79,6 +79,9 @@ CCL_NAMESPACE_BEGIN # ifdef WITH_OSL # define __OSL__ # endif +# ifdef WITH_PATH_GUIDING +# define __PATH_GUIDING__ +# endif # define __VOLUME_RECORD_ALL__ #endif /* !__KERNEL_GPU__ */ @@ -146,12 +149,14 @@ enum PathTraceDimension { PRNG_SURFACE_BSDF = 3, PRNG_SURFACE_AO = 4, PRNG_SURFACE_BEVEL = 5, + PRNG_SURFACE_BSDF_GUIDING = 6, /* Volume */ PRNG_VOLUME_PHASE = 3, PRNG_VOLUME_PHASE_CHANNEL = 4, PRNG_VOLUME_SCATTER_DISTANCE = 5, PRNG_VOLUME_OFFSET = 6, PRNG_VOLUME_SHADE_OFFSET = 7, + PRNG_VOLUME_PHASE_GUIDING = 8, /* Subsurface random walk bounces */ PRNG_SUBSURFACE_BSDF = 0, @@ -387,6 +392,14 @@ typedef enum PassType { PASS_SHADOW_CATCHER_SAMPLE_COUNT, PASS_SHADOW_CATCHER_MATTE, + /* Guiding related debug rendering passes */ + /* The estimated sample color from the PathSegmentStorage. If everything is integrated correctly + * the output should be similar to PASS_COMBINED. */ + PASS_GUIDING_COLOR, + /* The guiding probability at the first bounce. */ + PASS_GUIDING_PROBABILITY, + /* The avg. roughness at the first bounce. */ + PASS_GUIDING_AVG_ROUGHNESS, PASS_CATEGORY_DATA_END = 63, PASS_BAKE_PRIMITIVE, @@ -455,6 +468,16 @@ typedef enum LightType { LIGHT_TRIANGLE } LightType; +/* Guiding Distribution Type */ + +typedef enum GuidingDistributionType { + GUIDING_TYPE_PARALLAX_AWARE_VMM = 0, + GUIDING_TYPE_DIRECTIONAL_QUAD_TREE = 1, + GUIDING_TYPE_VMM = 2, + + GUIDING_NUM_TYPES, +} GuidingDistributionType; + /* Camera Type */ enum CameraType { CAMERA_PERSPECTIVE, CAMERA_ORTHOGRAPHIC, CAMERA_PANORAMA }; @@ -1502,6 +1525,9 @@ enum KernelFeatureFlag : uint32_t { /* MNEE. */ KERNEL_FEATURE_MNEE = (1U << 25U), + + /* Path guiding. */ + KERNEL_FEATURE_PATH_GUIDING = (1U << 26U), }; /* Shader node feature mask, to specialize shader evaluation for kernels. */ diff --git a/intern/cycles/scene/film.cpp b/intern/cycles/scene/film.cpp index a6a8f90a449..41ae73b2f27 100644 --- a/intern/cycles/scene/film.cpp +++ b/intern/cycles/scene/film.cpp @@ -200,6 +200,10 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) kfilm->pass_shadow_catcher_sample_count = PASS_UNUSED; kfilm->pass_shadow_catcher_matte = PASS_UNUSED; + kfilm->pass_guiding_color = PASS_UNUSED; + kfilm->pass_guiding_probability = PASS_UNUSED; + kfilm->pass_guiding_avg_roughness = PASS_UNUSED; + bool have_cryptomatte = false; bool have_aov_color = false; bool have_aov_value = false; @@ -382,6 +386,15 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) have_aov_value = true; } break; + case PASS_GUIDING_COLOR: + kfilm->pass_guiding_color = kfilm->pass_stride; + break; + case PASS_GUIDING_PROBABILITY: + kfilm->pass_guiding_probability = kfilm->pass_stride; + break; + case PASS_GUIDING_AVG_ROUGHNESS: + kfilm->pass_guiding_avg_roughness = kfilm->pass_stride; + break; default: assert(false); break; diff --git a/intern/cycles/scene/integrator.cpp b/intern/cycles/scene/integrator.cpp index e9cd753854f..ade4716242b 100644 --- a/intern/cycles/scene/integrator.cpp +++ b/intern/cycles/scene/integrator.cpp @@ -60,6 +60,25 @@ NODE_DEFINE(Integrator) SOCKET_INT(volume_max_steps, "Volume Max Steps", 1024); SOCKET_FLOAT(volume_step_rate, "Volume Step Rate", 1.0f); + static NodeEnum guiding_ditribution_enum; + guiding_ditribution_enum.insert("PARALLAX_AWARE_VMM", GUIDING_TYPE_PARALLAX_AWARE_VMM); + guiding_ditribution_enum.insert("DIRECTIONAL_QUAD_TREE", GUIDING_TYPE_DIRECTIONAL_QUAD_TREE); + guiding_ditribution_enum.insert("VMM", GUIDING_TYPE_VMM); + + SOCKET_BOOLEAN(use_guiding, "Guiding", false); + SOCKET_BOOLEAN(deterministic_guiding, "Deterministic Guiding", true); + SOCKET_BOOLEAN(use_surface_guiding, "Surface Guiding", true); + SOCKET_FLOAT(surface_guiding_probability, "Surface Guiding Probability", 0.5f); + SOCKET_BOOLEAN(use_volume_guiding, "Volume Guiding", true); + SOCKET_FLOAT(volume_guiding_probability, "Volume Guiding Probability", 0.5f); + SOCKET_INT(guiding_training_samples, "Training Samples", 128); + SOCKET_BOOLEAN(use_guiding_direct_light, "Guide Direct Light", true); + SOCKET_BOOLEAN(use_guiding_mis_weights, "Use MIS Weights", true); + SOCKET_ENUM(guiding_distribution_type, + "Guiding Distribution Type", + guiding_ditribution_enum, + GUIDING_TYPE_PARALLAX_AWARE_VMM); + SOCKET_BOOLEAN(caustics_reflective, "Reflective Caustics", true); SOCKET_BOOLEAN(caustics_refractive, "Refractive Caustics", true); SOCKET_FLOAT(filter_glossy, "Filter Glossy", 0.0f); @@ -209,6 +228,17 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene kintegrator->filter_closures |= FILTER_CLOSURE_TRANSPARENT; } + GuidingParams guiding_params = get_guiding_params(device); + kintegrator->use_guiding = guiding_params.use; + kintegrator->train_guiding = kintegrator->use_guiding; + kintegrator->use_surface_guiding = guiding_params.use_surface_guiding; + kintegrator->use_volume_guiding = guiding_params.use_volume_guiding; + kintegrator->surface_guiding_probability = surface_guiding_probability; + kintegrator->volume_guiding_probability = volume_guiding_probability; + kintegrator->use_guiding_direct_light = use_guiding_direct_light; + kintegrator->use_guiding_mis_weights = use_guiding_mis_weights; + kintegrator->guiding_distribution_type = guiding_params.type; + kintegrator->seed = seed; kintegrator->sample_clamp_direct = (sample_clamp_direct == 0.0f) ? FLT_MAX : @@ -354,4 +384,20 @@ DenoiseParams Integrator::get_denoise_params() const return denoise_params; } +GuidingParams Integrator::get_guiding_params(const Device *device) const +{ + const bool use = use_guiding && device->info.has_guiding; + + GuidingParams guiding_params; + guiding_params.use_surface_guiding = use && use_surface_guiding && + surface_guiding_probability > 0.0f; + guiding_params.use_volume_guiding = use && use_volume_guiding && + volume_guiding_probability > 0.0f; + guiding_params.use = guiding_params.use_surface_guiding || guiding_params.use_volume_guiding; + guiding_params.type = guiding_distribution_type; + guiding_params.training_samples = guiding_training_samples; + guiding_params.deterministic = deterministic_guiding; + + return guiding_params; +} CCL_NAMESPACE_END diff --git a/intern/cycles/scene/integrator.h b/intern/cycles/scene/integrator.h index d54a44b6177..8fdf53547b0 100644 --- a/intern/cycles/scene/integrator.h +++ b/intern/cycles/scene/integrator.h @@ -9,6 +9,7 @@ #include "device/denoise.h" /* For the parameters and type enum. */ #include "graph/node.h" #include "integrator/adaptive_sampling.h" +#include "integrator/guiding.h" CCL_NAMESPACE_BEGIN @@ -43,6 +44,17 @@ class Integrator : public Node { NODE_SOCKET_API(int, volume_max_steps) NODE_SOCKET_API(float, volume_step_rate) + NODE_SOCKET_API(bool, use_guiding); + NODE_SOCKET_API(bool, deterministic_guiding); + NODE_SOCKET_API(bool, use_surface_guiding); + NODE_SOCKET_API(float, surface_guiding_probability); + NODE_SOCKET_API(bool, use_volume_guiding); + NODE_SOCKET_API(float, volume_guiding_probability); + NODE_SOCKET_API(int, guiding_training_samples); + NODE_SOCKET_API(bool, use_guiding_direct_light); + NODE_SOCKET_API(bool, use_guiding_mis_weights); + NODE_SOCKET_API(GuidingDistributionType, guiding_distribution_type); + NODE_SOCKET_API(bool, caustics_reflective) NODE_SOCKET_API(bool, caustics_refractive) NODE_SOCKET_API(float, filter_glossy) @@ -105,6 +117,7 @@ class Integrator : public Node { AdaptiveSampling get_adaptive_sampling() const; DenoiseParams get_denoise_params() const; + GuidingParams get_guiding_params(const Device *device) const; }; CCL_NAMESPACE_END diff --git a/intern/cycles/scene/pass.cpp b/intern/cycles/scene/pass.cpp index c2f12355ac7..5833b45c409 100644 --- a/intern/cycles/scene/pass.cpp +++ b/intern/cycles/scene/pass.cpp @@ -96,6 +96,12 @@ const NodeEnum *Pass::get_type_enum() pass_type_enum.insert("bake_primitive", PASS_BAKE_PRIMITIVE); pass_type_enum.insert("bake_differential", PASS_BAKE_DIFFERENTIAL); + +#ifdef WITH_CYCLES_DEBUG + pass_type_enum.insert("guiding_color", PASS_GUIDING_COLOR); + pass_type_enum.insert("guiding_probability", PASS_GUIDING_PROBABILITY); + pass_type_enum.insert("guiding_avg_roughness", PASS_GUIDING_AVG_ROUGHNESS); +#endif } return &pass_type_enum; @@ -341,6 +347,15 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bo LOG(DFATAL) << "Unexpected pass type is used " << type; pass_info.num_components = 0; break; + case PASS_GUIDING_COLOR: + pass_info.num_components = 3; + break; + case PASS_GUIDING_PROBABILITY: + pass_info.num_components = 1; + break; + case PASS_GUIDING_AVG_ROUGHNESS: + pass_info.num_components = 1; + break; } return pass_info; diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index 478396ea98e..3a05bede7a3 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -439,9 +439,9 @@ bool Scene::need_data_update() film->is_modified() || procedural_manager->need_update()); } -bool Scene::need_reset() +bool Scene::need_reset(const bool check_camera) { - return need_data_update() || camera->is_modified(); + return need_data_update() || (check_camera && camera->is_modified()); } void Scene::reset() @@ -549,6 +549,10 @@ void Scene::update_kernel_features() kernel_features |= KERNEL_FEATURE_MNEE; } + if (integrator->get_guiding_params(device).use) { + kernel_features |= KERNEL_FEATURE_PATH_GUIDING; + } + if (bake_manager->get_baking()) { kernel_features |= KERNEL_FEATURE_BAKING; } diff --git a/intern/cycles/scene/scene.h b/intern/cycles/scene/scene.h index d1004bb7b66..d87cc37aafe 100644 --- a/intern/cycles/scene/scene.h +++ b/intern/cycles/scene/scene.h @@ -261,7 +261,7 @@ class Scene : public NodeOwner { float motion_shutter_time(); bool need_update(); - bool need_reset(); + bool need_reset(const bool check_camera = true); void reset(); void device_free(); diff --git a/intern/cycles/session/session.cpp b/intern/cycles/session/session.cpp index a2955da5480..a0eb3196a34 100644 --- a/intern/cycles/session/session.cpp +++ b/intern/cycles/session/session.cpp @@ -318,6 +318,13 @@ RenderWork Session::run_update_for_next_iteration() path_trace_->set_adaptive_sampling(adaptive_sampling); } + /* Update path guiding. */ + { + const GuidingParams guiding_params = scene->integrator->get_guiding_params(device); + const bool guiding_reset = (guiding_params.use) ? scene->need_reset(false) : false; + path_trace_->set_guiding_params(guiding_params, guiding_reset); + } + render_scheduler_.set_num_samples(params.samples); render_scheduler_.set_start_sample(params.sample_offset); render_scheduler_.set_time_limit(params.time_limit); diff --git a/intern/cycles/util/CMakeLists.txt b/intern/cycles/util/CMakeLists.txt index 997d574a3b0..57628f99e35 100644 --- a/intern/cycles/util/CMakeLists.txt +++ b/intern/cycles/util/CMakeLists.txt @@ -49,6 +49,7 @@ set(SRC_HEADERS foreach.h function.h guarded_allocator.h + guiding.h half.h hash.h ies.h diff --git a/intern/cycles/util/guiding.h b/intern/cycles/util/guiding.h new file mode 100644 index 00000000000..7dc74f0d8c0 --- /dev/null +++ b/intern/cycles/util/guiding.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2022 Blender Foundation */ + +#pragma once + +#ifdef WITH_PATH_GUIDING +# include +# include +#endif + +#include "util/system.h" + +CCL_NAMESPACE_BEGIN + +static int guiding_device_type() +{ +#ifdef WITH_PATH_GUIDING +# if defined(__ARM_NEON) + return 8; +# else +# if OPENPGL_VERSION_MINOR >= 4 + if (system_cpu_support_avx2()) { + return 8; + } +# endif + if (system_cpu_support_sse41()) { + return 4; + } + return 0; +# endif +#else + return 0; +#endif +} + +static inline bool guiding_supported() +{ + return guiding_device_type() != 0; +} + +CCL_NAMESPACE_END diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 27473061304..fe00cce2572 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -677,6 +677,11 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS) list(APPEND render_tests denoise) endif() + # Disabled until new OpenGL version with deterministic results. + #if(WITH_CYCLES_PATH_GUIDING) + # list(APPEND render_tests guiding) + #endif() + if(WITH_OPENGL_RENDER_TESTS) list(APPEND render_tests grease_pencil) endif() diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 10ba2ce552a..4f823f854bf 100644 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -56,6 +56,8 @@ BLACKLIST_GPU = [ # Inconsistent handling of overlapping objects. "T41143.blend", "visibility_particles.blend", + # No path guiding on GPU. + "guiding*.blend", ]