Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Herhoz <sebastian.herholz@intel.com>2022-09-21 18:58:34 +0300
committerBrecht Van Lommel <brecht@blender.org>2022-09-27 16:56:32 +0300
commit75a6d3abf75f3082adf5240ae34973844c0d9a09 (patch)
tree6967aea2480187db007cdc003bad14fbb372570d
parent6d19da0b2d468f099e0c1f56392ab8a1750d114f (diff)
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
-rw-r--r--CMakeLists.txt1
-rw-r--r--build_files/cmake/config/blender_full.cmake1
-rw-r--r--build_files/cmake/config/blender_release.cmake1
-rw-r--r--intern/cycles/CMakeLists.txt18
-rw-r--r--intern/cycles/blender/addon/engine.py5
-rw-r--r--intern/cycles/blender/addon/properties.py82
-rw-r--r--intern/cycles/blender/addon/ui.py59
-rw-r--r--intern/cycles/blender/python.cpp10
-rw-r--r--intern/cycles/blender/sync.cpp27
-rw-r--r--intern/cycles/cmake/external_libs.cmake23
-rw-r--r--intern/cycles/cmake/macros.cmake3
-rw-r--r--intern/cycles/device/cpu/device.cpp7
-rw-r--r--intern/cycles/device/cpu/device_impl.cpp18
-rw-r--r--intern/cycles/device/cpu/device_impl.h8
-rw-r--r--intern/cycles/device/cpu/kernel_thread_globals.cpp27
-rw-r--r--intern/cycles/device/cpu/kernel_thread_globals.h2
-rw-r--r--intern/cycles/device/device.cpp2
-rw-r--r--intern/cycles/device/device.h11
-rw-r--r--intern/cycles/integrator/CMakeLists.txt6
-rw-r--r--intern/cycles/integrator/guiding.h32
-rw-r--r--intern/cycles/integrator/path_trace.cpp129
-rw-r--r--intern/cycles/integrator/path_trace.h33
-rw-r--r--intern/cycles/integrator/path_trace_work.h7
-rw-r--r--intern/cycles/integrator/path_trace_work_cpu.cpp110
-rw-r--r--intern/cycles/integrator/path_trace_work_cpu.h17
-rw-r--r--intern/cycles/integrator/render_scheduler.cpp15
-rw-r--r--intern/cycles/integrator/render_scheduler.h6
-rw-r--r--intern/cycles/kernel/CMakeLists.txt1
-rw-r--r--intern/cycles/kernel/data_template.h17
-rw-r--r--intern/cycles/kernel/device/cpu/globals.h19
-rw-r--r--intern/cycles/kernel/integrator/guiding.h542
-rw-r--r--intern/cycles/kernel/integrator/intersect_closest.h11
-rw-r--r--intern/cycles/kernel/integrator/mnee.h2
-rw-r--r--intern/cycles/kernel/integrator/path_state.h11
-rw-r--r--intern/cycles/kernel/integrator/shade_background.h3
-rw-r--r--intern/cycles/kernel/integrator/shade_light.h3
-rw-r--r--intern/cycles/kernel/integrator/shade_shadow.h2
-rw-r--r--intern/cycles/kernel/integrator/shade_surface.h104
-rw-r--r--intern/cycles/kernel/integrator/shade_volume.h125
-rw-r--r--intern/cycles/kernel/integrator/shadow_state_template.h10
-rw-r--r--intern/cycles/kernel/integrator/state.h4
-rw-r--r--intern/cycles/kernel/integrator/state_template.h33
-rw-r--r--intern/cycles/kernel/integrator/subsurface.h3
-rw-r--r--intern/cycles/kernel/integrator/subsurface_disk.h9
-rw-r--r--intern/cycles/kernel/integrator/subsurface_random_walk.h16
-rw-r--r--intern/cycles/kernel/integrator/surface_shader.h281
-rw-r--r--intern/cycles/kernel/integrator/volume_shader.h267
-rw-r--r--intern/cycles/kernel/types.h26
-rw-r--r--intern/cycles/scene/film.cpp13
-rw-r--r--intern/cycles/scene/integrator.cpp46
-rw-r--r--intern/cycles/scene/integrator.h13
-rw-r--r--intern/cycles/scene/pass.cpp15
-rw-r--r--intern/cycles/scene/scene.cpp8
-rw-r--r--intern/cycles/scene/scene.h2
-rw-r--r--intern/cycles/session/session.cpp7
-rw-r--r--intern/cycles/util/CMakeLists.txt1
-rw-r--r--intern/cycles/util/guiding.h41
-rw-r--r--tests/python/CMakeLists.txt5
-rw-r--r--tests/python/cycles_render_tests.py2
59 files changed, 2181 insertions, 121 deletions
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()
###########################################################################
@@ -270,6 +274,25 @@ if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OSL)
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<DeviceInfo> &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<openpgl::cpp::Device>(PGL_DEVICE_TYPE_CPU_8);
+ }
+ else if (guiding_device_type() == 4) {
+ guiding_device = make_unique<openpgl::cpp::Device>(PGL_DEVICE_TYPE_CPU_4);
+ }
+ }
+ return guiding_device.get();
+#else
+ return nullptr;
+#endif
+}
+
void CPUDevice::get_cpu_kernel_thread_globals(
vector<CPUKernelThreadGlobals> &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<openpgl::cpp::Device> 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<CPUKernelThreadGlobals> &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<OSLGlobals *>(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<KernelGlobalsCPU *>(this) = *static_cast<KernelGlobalsCPU *>(&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<DeviceInfo> &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<DeviceInfo> &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<openpgl::cpp::Device *>(
+ device_->get_guiding_device());
+ if (guiding_device) {
+ guiding_sample_data_storage_ = make_unique<openpgl::cpp::SampleStorage>();
+ guiding_field_ = make_unique<openpgl::cpp::Field>(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 &params, const bool reset);
+
/* Sets output driver for render buffer output. */
void set_output_driver(unique_ptr<OutputDriver> 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<PathTraceWork> 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<openpgl::cpp::Field> guiding_field_;
+
+ /* The storage container which holds the training data/samples generated during the last
+ * rendering iteration. */
+ unique_ptr<openpgl::cpp::SampleStorage> 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 &params)
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 (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
- return LABEL_NONE;
+#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;
+ }
+
+ 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<node_feature_mask>(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;
+
+# 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;
+ }
- const int label = volume_shader_phase_sample(
- kg, sd, phases, rand_phase, &phase_eval, &phase_omega_in, &phase_pdf);
+ 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;
+ }
- 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,
@@ -117,88 +231,139 @@ 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 ShaderVolumePhases *phases,
+ ccl_private const ShaderVolumeClosure *svc,
const float3 omega_in,
ccl_private BsdfEval *phase_eval)
{
- bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
+ 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 _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f);
+ return phase_pdf;
}
-ccl_device int volume_shader_phase_sample(KernelGlobals kg,
+ccl_device float volume_shader_phase_eval(KernelGlobals kg,
+ IntegratorState state,
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)
+ const float3 omega_in,
+ ccl_private BsdfEval *phase_eval)
{
- int sampled = 0;
+ bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
- if (phases->num_closure > 1) {
- /* pick a phase closure based on sample weights */
- float sum = 0.0f;
+ float pdf = _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f);
- for (sampled = 0; sampled < phases->num_closure; sampled++) {
- ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
- sum += svc->sample_weight;
- }
+# 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
- float r = rand_phase.x * sum;
- float partial_sum = 0.0f;
+ return pdf;
+}
- for (sampled = 0; sampled < phases->num_closure; sampled++) {
- ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
- float next_sum = partial_sum + svc->sample_weight;
+# 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)
+{
+ const bool use_volume_guiding = state->guiding.use_volume_guiding;
+ const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob;
+
+ /* 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);
+ }
- if (r <= next_sum) {
- /* Rescale to reuse for BSDF direction sample. */
- rand_phase.x = (r - partial_sum) / svc->sample_weight;
- break;
- }
+ /* Initialize to zero. */
+ int label = LABEL_NONE;
+ Spectrum eval = zero_spectrum();
- partial_sum = next_sum;
- }
+ *unguided_phase_pdf = 0.0f;
+ float guide_pdf = 0.0f;
+ *sampled_roughness = 1.0f - fabsf(svc->g);
+
+ bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
- if (sampled == phases->num_closure) {
- *pdf = 0.0f;
- return LABEL_NONE;
+ 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;
+ }
- /* 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;
- Spectrum eval = zero_spectrum();
-
- *pdf = 0.0f;
- label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, 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());
+ }
- if (*pdf != 0.0f) {
- bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval);
+ 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 <openpgl/cpp/OpenPGL.h>
+# include <openpgl/version.h>
+#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",
]