forked from bartvdbraak/blender
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
This commit is contained in:
parent
6d19da0b2d
commit
75a6d3abf7
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -104,6 +104,10 @@ if(CYCLES_STANDALONE_REPOSITORY)
|
||||
else()
|
||||
unset(_cycles_lib_dir)
|
||||
endif()
|
||||
else()
|
||||
if(EXISTS ${LIBDIR})
|
||||
set(_cycles_lib_dir ${LIBDIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
@ -269,6 +273,25 @@ if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OSL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenPGL
|
||||
###########################################################################
|
||||
|
||||
if(WITH_CYCLES_PATH_GUIDING)
|
||||
if(EXISTS ${_cycles_lib_dir})
|
||||
set(openpgl_DIR ${_cycles_lib_dir}/openpgl/lib/cmake/openpgl)
|
||||
endif()
|
||||
|
||||
find_package(openpgl QUIET)
|
||||
if(openpgl_FOUND)
|
||||
get_target_property(OPENPGL_LIBRARIES openpgl::openpgl LOCATION)
|
||||
get_target_property(OPENPGL_INCLUDE_DIR openpgl::openpgl INTERFACE_INCLUDE_DIRECTORIES)
|
||||
else()
|
||||
set(WITH_CYCLES_PATH_GUIDING OFF)
|
||||
message(STATUS "OpenPGL not found, disabling WITH_CYCLES_PATH_GUIDING")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
###########################################################################
|
||||
# OpenColorIO
|
||||
###########################################################################
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -36,7 +36,7 @@ class CPUKernelThreadGlobals : public KernelGlobalsCPU {
|
||||
void stop_profiling();
|
||||
|
||||
protected:
|
||||
void reset_runtime_memory();
|
||||
void clear_runtime_pointers();
|
||||
|
||||
Profiler &cpu_profiler_;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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. */
|
||||
|
@ -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})
|
||||
|
||||
|
32
intern/cycles/integrator/guiding.h
Normal file
32
intern/cycles/integrator/guiding.h
Normal file
@ -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
|
@ -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
|
||||
|
@ -4,11 +4,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "integrator/denoiser.h"
|
||||
#include "integrator/guiding.h"
|
||||
#include "integrator/pass_accessor.h"
|
||||
#include "integrator/path_trace_work.h"
|
||||
#include "integrator/work_balancer.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/function.h"
|
||||
#include "util/guiding.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/unique_ptr.h"
|
||||
#include "util/vector.h"
|
||||
@ -89,6 +93,10 @@ class PathTrace {
|
||||
* Use this to configure the adaptive sampler before rendering any samples. */
|
||||
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling);
|
||||
|
||||
/* Set the parameters for guiding.
|
||||
* Use to setup the guiding structures before each rendering iteration.*/
|
||||
void set_guiding_params(const GuidingParams ¶ms, const bool reset);
|
||||
|
||||
/* Sets output driver for render buffer output. */
|
||||
void set_output_driver(unique_ptr<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. */
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -45,6 +45,11 @@ void RenderScheduler::set_denoiser_params(const DenoiseParams ¶ms)
|
||||
denoiser_params_ = params;
|
||||
}
|
||||
|
||||
void RenderScheduler::set_limit_samples_per_update(const int limit_samples)
|
||||
{
|
||||
limit_samples_per_update_ = limit_samples;
|
||||
}
|
||||
|
||||
void RenderScheduler::set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling)
|
||||
{
|
||||
adaptive_sampling_ = adaptive_sampling;
|
||||
@ -760,7 +765,13 @@ int RenderScheduler::calculate_num_samples_per_update() const
|
||||
|
||||
const double update_interval_in_seconds = guess_display_update_interval_in_seconds();
|
||||
|
||||
return max(int(num_samples_in_second * update_interval_in_seconds), 1);
|
||||
int num_samples_per_update = max(int(num_samples_in_second * update_interval_in_seconds), 1);
|
||||
|
||||
if (limit_samples_per_update_) {
|
||||
num_samples_per_update = min(limit_samples_per_update_, num_samples_per_update);
|
||||
}
|
||||
|
||||
return num_samples_per_update;
|
||||
}
|
||||
|
||||
int RenderScheduler::get_start_sample_to_path_trace() const
|
||||
@ -808,7 +819,7 @@ int RenderScheduler::get_num_samples_to_path_trace() const
|
||||
return 1;
|
||||
}
|
||||
|
||||
const int num_samples_per_update = calculate_num_samples_per_update();
|
||||
int num_samples_per_update = calculate_num_samples_per_update();
|
||||
const int path_trace_start_sample = get_start_sample_to_path_trace();
|
||||
|
||||
/* Round number of samples to a power of two, so that division of path states into tiles goes in
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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 **** */
|
||||
|
542
intern/cycles/kernel/integrator/guiding.h
Normal file
542
intern/cycles/kernel/integrator/guiding.h
Normal file
@ -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
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "kernel/integrator/mnee.h"
|
||||
|
||||
#include "kernel/integrator/guiding.h"
|
||||
#include "kernel/integrator/path_state.h"
|
||||
#include "kernel/integrator/subsurface.h"
|
||||
#include "kernel/integrator/surface_shader.h"
|
||||
@ -101,7 +102,7 @@ ccl_device_forceinline bool integrate_surface_holdout(KernelGlobals kg,
|
||||
}
|
||||
|
||||
ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
|
||||
ConstIntegratorState state,
|
||||
IntegratorState state,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_global float *ccl_restrict
|
||||
render_buffer)
|
||||
@ -128,6 +129,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
|
||||
mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
|
||||
}
|
||||
|
||||
guiding_record_surface_emission(kg, state, L, mis_weight);
|
||||
film_write_surface_emission(
|
||||
kg, state, L, mis_weight, render_buffer, object_lightgroup(kg, sd->object));
|
||||
}
|
||||
@ -210,7 +212,7 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
||||
}
|
||||
|
||||
/* Evaluate BSDF. */
|
||||
const float bsdf_pdf = surface_shader_bsdf_eval(kg, sd, ls.D, &bsdf_eval, ls.shader);
|
||||
const float bsdf_pdf = surface_shader_bsdf_eval(kg, state, sd, ls.D, &bsdf_eval, ls.shader);
|
||||
bsdf_eval_mul(&bsdf_eval, light_eval / ls.pdf);
|
||||
|
||||
if (ls.shader & SHADER_USE_MIS) {
|
||||
@ -258,8 +260,8 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
||||
/* Copy state from main path to shadow path. */
|
||||
uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
shadow_flag |= (is_light) ? PATH_RAY_SHADOW_FOR_LIGHT : 0;
|
||||
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput) *
|
||||
bsdf_eval_sum(&bsdf_eval);
|
||||
const Spectrum unlit_throughput = INTEGRATOR_STATE(state, path, throughput);
|
||||
const Spectrum throughput = unlit_throughput * bsdf_eval_sum(&bsdf_eval);
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
|
||||
PackedSpectrum pass_diffuse_weight;
|
||||
@ -329,6 +331,11 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
||||
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
|
||||
ls.group + 1 :
|
||||
kernel_data.background.lightgroup + 1;
|
||||
#ifdef __PATH_GUIDING__
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput;
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
|
||||
state, guiding, path_segment);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Path tracing: bounce off or through surface with new direction. */
|
||||
@ -354,7 +361,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
||||
#endif
|
||||
|
||||
/* BSDF closure, sample direction. */
|
||||
float bsdf_pdf;
|
||||
float bsdf_pdf = 0.0f, unguided_bsdf_pdf = 0.0f;
|
||||
BsdfEval bsdf_eval ccl_optional_struct_init;
|
||||
float3 bsdf_omega_in ccl_optional_struct_init;
|
||||
int label;
|
||||
@ -362,18 +369,44 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
||||
float2 bsdf_sampled_roughness = make_float2(1.0f, 1.0f);
|
||||
float bsdf_eta = 1.0f;
|
||||
|
||||
label = surface_shader_bsdf_sample_closure(kg,
|
||||
sd,
|
||||
sc,
|
||||
rand_bsdf,
|
||||
&bsdf_eval,
|
||||
&bsdf_omega_in,
|
||||
&bsdf_pdf,
|
||||
&bsdf_sampled_roughness,
|
||||
&bsdf_eta);
|
||||
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
|
||||
if (kernel_data.integrator.use_surface_guiding) {
|
||||
label = surface_shader_bsdf_guided_sample_closure(kg,
|
||||
state,
|
||||
sd,
|
||||
sc,
|
||||
rand_bsdf,
|
||||
&bsdf_eval,
|
||||
&bsdf_omega_in,
|
||||
&bsdf_pdf,
|
||||
&unguided_bsdf_pdf,
|
||||
&bsdf_sampled_roughness,
|
||||
&bsdf_eta);
|
||||
|
||||
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
|
||||
return LABEL_NONE;
|
||||
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
|
||||
return LABEL_NONE;
|
||||
}
|
||||
|
||||
INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= bsdf_pdf / unguided_bsdf_pdf;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
label = surface_shader_bsdf_sample_closure(kg,
|
||||
sd,
|
||||
sc,
|
||||
rand_bsdf,
|
||||
&bsdf_eval,
|
||||
&bsdf_omega_in,
|
||||
&bsdf_pdf,
|
||||
&bsdf_sampled_roughness,
|
||||
&bsdf_eta);
|
||||
|
||||
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
|
||||
return LABEL_NONE;
|
||||
}
|
||||
|
||||
unguided_bsdf_pdf = bsdf_pdf;
|
||||
}
|
||||
|
||||
if (label & LABEL_TRANSPARENT) {
|
||||
@ -393,9 +426,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
||||
}
|
||||
|
||||
/* Update throughput. */
|
||||
Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
|
||||
throughput *= bsdf_eval_sum(&bsdf_eval) / bsdf_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput;
|
||||
const Spectrum bsdf_weight = bsdf_eval_sum(&bsdf_eval) / bsdf_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) *= bsdf_weight;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
|
||||
if (INTEGRATOR_STATE(state, path, bounce) == 0) {
|
||||
@ -410,10 +442,21 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
||||
if (!(label & LABEL_TRANSPARENT)) {
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
}
|
||||
|
||||
path_state_next(kg, state, label);
|
||||
|
||||
guiding_record_surface_bounce(kg,
|
||||
state,
|
||||
sd,
|
||||
bsdf_weight,
|
||||
bsdf_pdf,
|
||||
sd->N,
|
||||
normalize(bsdf_omega_in),
|
||||
bsdf_sampled_roughness,
|
||||
bsdf_eta);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@ -435,14 +478,15 @@ ccl_device_forceinline int integrate_surface_volume_only_bounce(IntegratorState
|
||||
ccl_device_forceinline bool integrate_surface_terminate(IntegratorState state,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const float probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ?
|
||||
0.0f :
|
||||
INTEGRATOR_STATE(state, path, continuation_probability);
|
||||
if (probability == 0.0f) {
|
||||
const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ?
|
||||
0.0f :
|
||||
INTEGRATOR_STATE(
|
||||
state, path, continuation_probability);
|
||||
if (continuation_probability == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
else if (probability != 1.0f) {
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) /= probability;
|
||||
else if (continuation_probability != 1.0f) {
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) /= continuation_probability;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -550,6 +594,8 @@ ccl_device bool integrate_surface(KernelGlobals kg,
|
||||
#ifdef __VOLUME__
|
||||
if (!(sd.flag & SD_HAS_ONLY_VOLUME)) {
|
||||
#endif
|
||||
guiding_record_surface_segment(kg, state, &sd);
|
||||
|
||||
#ifdef __SUBSURFACE__
|
||||
/* Can skip shader evaluation for BSSRDF exit point without bump mapping. */
|
||||
if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP)))
|
||||
@ -615,6 +661,10 @@ ccl_device bool integrate_surface(KernelGlobals kg,
|
||||
RNGState rng_state;
|
||||
path_state_rng_load(state, &rng_state);
|
||||
|
||||
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
|
||||
surface_shader_prepare_guiding(kg, state, &sd, &rng_state);
|
||||
guiding_write_debug_passes(kg, state, &sd, render_buffer);
|
||||
#endif
|
||||
/* Direct light. */
|
||||
PROFILING_EVENT(PROFILING_SHADE_SURFACE_DIRECT_LIGHT);
|
||||
integrate_surface_direct_light<node_feature_mask>(kg, state, &sd, &rng_state);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "kernel/film/denoising_passes.h"
|
||||
#include "kernel/film/light_passes.h"
|
||||
|
||||
#include "kernel/integrator/guiding.h"
|
||||
#include "kernel/integrator/intersect_closest.h"
|
||||
#include "kernel/integrator/path_state.h"
|
||||
#include "kernel/integrator/volume_shader.h"
|
||||
@ -612,6 +613,7 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
|
||||
const Spectrum emission = volume_emission_integrate(
|
||||
&coeff, closure_flag, transmittance, dt);
|
||||
accum_emission += result.indirect_throughput * emission;
|
||||
guiding_record_volume_emission(kg, state, emission);
|
||||
}
|
||||
}
|
||||
|
||||
@ -761,7 +763,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
|
||||
/* Evaluate BSDF. */
|
||||
BsdfEval phase_eval ccl_optional_struct_init;
|
||||
const float phase_pdf = volume_shader_phase_eval(kg, sd, phases, ls->D, &phase_eval);
|
||||
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval);
|
||||
|
||||
if (ls->shader & SHADER_USE_MIS) {
|
||||
float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
|
||||
@ -848,6 +850,12 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
ls->group + 1 :
|
||||
kernel_data.background.lightgroup + 1;
|
||||
|
||||
# ifdef __PATH_GUIDING__
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput;
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
|
||||
state, guiding, path_segment);
|
||||
# endif
|
||||
|
||||
integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state);
|
||||
}
|
||||
|
||||
@ -861,18 +869,54 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
{
|
||||
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_INDIRECT_LIGHT);
|
||||
|
||||
const float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE);
|
||||
float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE);
|
||||
|
||||
ccl_private const ShaderVolumeClosure *svc = volume_shader_phase_pick(phases, &rand_phase);
|
||||
|
||||
/* Phase closure, sample direction. */
|
||||
float phase_pdf;
|
||||
float phase_pdf = 0.0f, unguided_phase_pdf = 0.0f;
|
||||
BsdfEval phase_eval ccl_optional_struct_init;
|
||||
float3 phase_omega_in ccl_optional_struct_init;
|
||||
float sampled_roughness = 1.0f;
|
||||
int label;
|
||||
|
||||
const int label = volume_shader_phase_sample(
|
||||
kg, sd, phases, rand_phase, &phase_eval, &phase_omega_in, &phase_pdf);
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
|
||||
if (kernel_data.integrator.use_guiding) {
|
||||
label = volume_shader_phase_guided_sample(kg,
|
||||
state,
|
||||
sd,
|
||||
svc,
|
||||
rand_phase,
|
||||
&phase_eval,
|
||||
&phase_omega_in,
|
||||
&phase_pdf,
|
||||
&unguided_phase_pdf,
|
||||
&sampled_roughness);
|
||||
|
||||
if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) {
|
||||
return false;
|
||||
if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= phase_pdf / unguided_phase_pdf;
|
||||
}
|
||||
else
|
||||
# endif
|
||||
{
|
||||
label = volume_shader_phase_sample(kg,
|
||||
sd,
|
||||
phases,
|
||||
svc,
|
||||
rand_phase,
|
||||
&phase_eval,
|
||||
&phase_omega_in,
|
||||
&phase_pdf,
|
||||
&sampled_roughness);
|
||||
|
||||
if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unguided_phase_pdf = phase_pdf;
|
||||
}
|
||||
|
||||
/* Setup ray. */
|
||||
@ -887,9 +931,15 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
INTEGRATOR_STATE_WRITE(state, isect, prim) = sd->prim;
|
||||
INTEGRATOR_STATE_WRITE(state, isect, object) = sd->object;
|
||||
|
||||
const Spectrum phase_weight = bsdf_eval_sum(&phase_eval) / phase_pdf;
|
||||
|
||||
/* Add phase function sampling data to the path segment. */
|
||||
guiding_record_volume_bounce(
|
||||
kg, state, sd, phase_weight, phase_pdf, normalize(phase_omega_in), sampled_roughness);
|
||||
|
||||
/* Update throughput. */
|
||||
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
|
||||
const Spectrum throughput_phase = throughput * bsdf_eval_sum(&phase_eval) / phase_pdf;
|
||||
const Spectrum throughput_phase = throughput * phase_weight;
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput_phase;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
|
||||
@ -900,7 +950,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
/* Update path state */
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
|
||||
path_state_next(kg, state, label);
|
||||
return true;
|
||||
@ -939,6 +989,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
VOLUME_READ_LAMBDA(integrator_state_read_volume_stack(state, i))
|
||||
const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass);
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput);
|
||||
# endif
|
||||
|
||||
/* TODO: expensive to zero closures? */
|
||||
VolumeIntegrateResult result = {};
|
||||
volume_integrate_heterogeneous(kg,
|
||||
@ -956,17 +1010,50 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
* to be terminated. That will shading evaluating to leave out any scattering closures,
|
||||
* but emission and absorption are still handled for multiple importance sampling. */
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
const float probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ?
|
||||
0.0f :
|
||||
INTEGRATOR_STATE(state, path, continuation_probability);
|
||||
if (probability == 0.0f) {
|
||||
const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ?
|
||||
0.0f :
|
||||
INTEGRATOR_STATE(
|
||||
state, path, continuation_probability);
|
||||
if (continuation_probability == 0.0f) {
|
||||
return VOLUME_PATH_MISSED;
|
||||
}
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
bool guiding_generated_new_segment = false;
|
||||
if (kernel_data.integrator.use_guiding) {
|
||||
/* Record transmittance using change in throughput. */
|
||||
float3 transmittance_weight = spectrum_to_rgb(
|
||||
safe_divide_color(result.indirect_throughput, initial_throughput));
|
||||
guiding_record_volume_transmission(kg, state, transmittance_weight);
|
||||
|
||||
if (result.indirect_scatter) {
|
||||
const float3 P = ray->P + result.indirect_t * ray->D;
|
||||
|
||||
/* Record volume segment up to direct scatter position.
|
||||
* TODO: volume segment is wrong when direct_t and indirect_t. */
|
||||
if (result.direct_scatter && (result.direct_t == result.indirect_t)) {
|
||||
guiding_record_volume_segment(kg, state, P, sd.I);
|
||||
guiding_generated_new_segment = true;
|
||||
}
|
||||
|
||||
# if PATH_GUIDING_LEVEL >= 4
|
||||
/* TODO: this position will be wrong for direct light pdf computation,
|
||||
* since the direct light position may be different? */
|
||||
volume_shader_prepare_guiding(
|
||||
kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method);
|
||||
# endif
|
||||
}
|
||||
else {
|
||||
/* No guiding if we don't scatter. */
|
||||
state->guiding.use_volume_guiding = false;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
/* Direct light. */
|
||||
if (result.direct_scatter) {
|
||||
const float3 direct_P = ray->P + result.direct_t * ray->D;
|
||||
result.direct_throughput /= probability;
|
||||
result.direct_throughput /= continuation_probability;
|
||||
integrate_volume_direct_light(kg,
|
||||
state,
|
||||
&sd,
|
||||
@ -979,16 +1066,22 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
|
||||
/* Indirect light.
|
||||
*
|
||||
* Only divide throughput by probability if we scatter. For the attenuation
|
||||
* Only divide throughput by continuation_probability if we scatter. For the attenuation
|
||||
* case the next surface will already do this division. */
|
||||
if (result.indirect_scatter) {
|
||||
result.indirect_throughput /= probability;
|
||||
result.indirect_throughput /= continuation_probability;
|
||||
}
|
||||
INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput;
|
||||
|
||||
if (result.indirect_scatter) {
|
||||
sd.P = ray->P + result.indirect_t * ray->D;
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
|
||||
if (!guiding_generated_new_segment) {
|
||||
guiding_record_volume_segment(kg, state, sd.P, sd.I);
|
||||
}
|
||||
# endif
|
||||
|
||||
if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) {
|
||||
return VOLUME_PATH_SCATTERED;
|
||||
}
|
||||
|
@ -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 *******************************/
|
||||
|
@ -31,6 +31,10 @@
|
||||
|
||||
#include "util/types.h"
|
||||
|
||||
#ifdef __PATH_GUIDING__
|
||||
# include "util/guiding.h"
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ CCL_NAMESPACE_BEGIN
|
||||
#ifdef __VOLUME__
|
||||
|
||||
/* Merging */
|
||||
|
||||
ccl_device_inline void volume_shader_merge_closures(ccl_private ShaderData *sd)
|
||||
{
|
||||
/* Merge identical closures to save closure space with stacked volumes. */
|
||||
@ -88,6 +89,119 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases
|
||||
}
|
||||
}
|
||||
|
||||
/* Guiding */
|
||||
|
||||
# ifdef __PATH_GUIDING__
|
||||
ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private ShaderData *sd,
|
||||
ccl_private const RNGState *rng_state,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
ccl_private ShaderVolumePhases *phases,
|
||||
const VolumeSampleMethod direct_sample_method)
|
||||
{
|
||||
/* Have any phase functions to guide? */
|
||||
const int num_phases = phases->num_closure;
|
||||
if (!kernel_data.integrator.use_volume_guiding || num_phases == 0) {
|
||||
state->guiding.use_volume_guiding = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability;
|
||||
float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING);
|
||||
|
||||
/* If we have more than one ohase function we select one random based on its
|
||||
* sample weight to caclulate the product distribution for guiding. */
|
||||
int phase_id = 0;
|
||||
float phase_weight = 1.0f;
|
||||
|
||||
if (num_phases > 1) {
|
||||
/* Pick a phase closure based on sample weights. */
|
||||
float sum = 0.0f;
|
||||
|
||||
for (phase_id = 0; phase_id < num_phases; phase_id++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id];
|
||||
sum += svc->sample_weight;
|
||||
}
|
||||
|
||||
float r = rand_phase_guiding * sum;
|
||||
float partial_sum = 0.0f;
|
||||
|
||||
for (phase_id = 0; phase_id < num_phases; phase_id++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id];
|
||||
float next_sum = partial_sum + svc->sample_weight;
|
||||
|
||||
if (r <= next_sum) {
|
||||
/* Rescale to reuse. */
|
||||
rand_phase_guiding = (r - partial_sum) / svc->sample_weight;
|
||||
phase_weight = svc->sample_weight / sum;
|
||||
break;
|
||||
}
|
||||
|
||||
partial_sum = next_sum;
|
||||
}
|
||||
|
||||
/* Adjust the sample weight of the component used for guiding. */
|
||||
phases->closure[phase_id].sample_weight *= volume_guiding_probability;
|
||||
}
|
||||
|
||||
/* Init guiding for selected phase function. */
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id];
|
||||
if (!guiding_phase_init(kg, state, P, D, svc->g, rand_phase_guiding)) {
|
||||
state->guiding.use_volume_guiding = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state->guiding.use_volume_guiding = true;
|
||||
state->guiding.sample_volume_guiding_rand = rand_phase_guiding;
|
||||
state->guiding.volume_guiding_sampling_prob = volume_guiding_probability * phase_weight;
|
||||
|
||||
kernel_assert(state->guiding.volume_guiding_sampling_prob > 0.0f &&
|
||||
state->guiding.volume_guiding_sampling_prob <= 1.0f);
|
||||
}
|
||||
# endif
|
||||
|
||||
/* Phase Evaluation & Sampling */
|
||||
|
||||
/* Randomly sample a volume phase function proportional to ShaderClosure.sample_weight. */
|
||||
ccl_device_inline ccl_private const ShaderVolumeClosure *volume_shader_phase_pick(
|
||||
ccl_private const ShaderVolumePhases *phases, ccl_private float2 *rand_phase)
|
||||
{
|
||||
int sampled = 0;
|
||||
|
||||
if (phases->num_closure > 1) {
|
||||
/* pick a phase closure based on sample weights */
|
||||
float sum = 0.0f;
|
||||
|
||||
for (int i = 0; i < phases->num_closure; i++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
|
||||
sum += svc->sample_weight;
|
||||
}
|
||||
|
||||
float r = (*rand_phase).x * sum;
|
||||
float partial_sum = 0.0f;
|
||||
|
||||
for (int i = 0; i < phases->num_closure; i++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[i];
|
||||
float next_sum = partial_sum + svc->sample_weight;
|
||||
|
||||
if (r <= next_sum) {
|
||||
/* Rescale to reuse for volume phase direction sample. */
|
||||
sampled = i;
|
||||
(*rand_phase).x = (r - partial_sum) / svc->sample_weight;
|
||||
break;
|
||||
}
|
||||
|
||||
partial_sum = next_sum;
|
||||
}
|
||||
}
|
||||
|
||||
/* todo: this isn't quite correct, we don't weight anisotropy properly
|
||||
* depending on color channels, even if this is perhaps not a common case */
|
||||
return &phases->closure[sampled];
|
||||
}
|
||||
|
||||
ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumePhases *phases,
|
||||
const float3 omega_in,
|
||||
@ -116,6 +230,23 @@ ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderDa
|
||||
}
|
||||
|
||||
ccl_device float volume_shader_phase_eval(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumeClosure *svc,
|
||||
const float3 omega_in,
|
||||
ccl_private BsdfEval *phase_eval)
|
||||
{
|
||||
float phase_pdf = 0.0f;
|
||||
Spectrum eval = volume_phase_eval(sd, svc, omega_in, &phase_pdf);
|
||||
|
||||
if (phase_pdf != 0.0f) {
|
||||
bsdf_eval_accum(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval);
|
||||
}
|
||||
|
||||
return phase_pdf;
|
||||
}
|
||||
|
||||
ccl_device float volume_shader_phase_eval(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumePhases *phases,
|
||||
const float3 omega_in,
|
||||
@ -123,82 +254,116 @@ ccl_device float volume_shader_phase_eval(KernelGlobals kg,
|
||||
{
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
|
||||
|
||||
return _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f);
|
||||
float pdf = _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f);
|
||||
|
||||
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
|
||||
if (state->guiding.use_volume_guiding) {
|
||||
const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob;
|
||||
const float guide_pdf = guiding_phase_pdf(kg, state, omega_in);
|
||||
pdf = (guiding_sampling_prob * guide_pdf) + (1.0f - guiding_sampling_prob) * pdf;
|
||||
}
|
||||
# endif
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
ccl_device int volume_shader_phase_sample(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumePhases *phases,
|
||||
float2 rand_phase,
|
||||
ccl_private BsdfEval *phase_eval,
|
||||
ccl_private float3 *omega_in,
|
||||
ccl_private float *pdf)
|
||||
# ifdef __PATH_GUIDING__
|
||||
ccl_device int volume_shader_phase_guided_sample(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumeClosure *svc,
|
||||
const float2 rand_phase,
|
||||
ccl_private BsdfEval *phase_eval,
|
||||
ccl_private float3 *omega_in,
|
||||
ccl_private float *phase_pdf,
|
||||
ccl_private float *unguided_phase_pdf,
|
||||
ccl_private float *sampled_roughness)
|
||||
{
|
||||
int sampled = 0;
|
||||
const bool use_volume_guiding = state->guiding.use_volume_guiding;
|
||||
const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob;
|
||||
|
||||
if (phases->num_closure > 1) {
|
||||
/* pick a phase closure based on sample weights */
|
||||
float sum = 0.0f;
|
||||
|
||||
for (sampled = 0; sampled < phases->num_closure; sampled++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
|
||||
sum += svc->sample_weight;
|
||||
}
|
||||
|
||||
float r = rand_phase.x * sum;
|
||||
float partial_sum = 0.0f;
|
||||
|
||||
for (sampled = 0; sampled < phases->num_closure; sampled++) {
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
|
||||
float next_sum = partial_sum + svc->sample_weight;
|
||||
|
||||
if (r <= next_sum) {
|
||||
/* Rescale to reuse for BSDF direction sample. */
|
||||
rand_phase.x = (r - partial_sum) / svc->sample_weight;
|
||||
break;
|
||||
}
|
||||
|
||||
partial_sum = next_sum;
|
||||
}
|
||||
|
||||
if (sampled == phases->num_closure) {
|
||||
*pdf = 0.0f;
|
||||
return LABEL_NONE;
|
||||
}
|
||||
/* Decide between sampling guiding distribution and phase. */
|
||||
float rand_phase_guiding = state->guiding.sample_volume_guiding_rand;
|
||||
bool sample_guiding = false;
|
||||
if (use_volume_guiding && rand_phase_guiding < guiding_sampling_prob) {
|
||||
sample_guiding = true;
|
||||
rand_phase_guiding /= guiding_sampling_prob;
|
||||
}
|
||||
else {
|
||||
rand_phase_guiding -= guiding_sampling_prob;
|
||||
rand_phase_guiding /= (1.0f - guiding_sampling_prob);
|
||||
}
|
||||
|
||||
/* todo: this isn't quite correct, we don't weight anisotropy properly
|
||||
* depending on color channels, even if this is perhaps not a common case */
|
||||
ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled];
|
||||
int label;
|
||||
/* Initialize to zero. */
|
||||
int label = LABEL_NONE;
|
||||
Spectrum eval = zero_spectrum();
|
||||
|
||||
*pdf = 0.0f;
|
||||
label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf);
|
||||
*unguided_phase_pdf = 0.0f;
|
||||
float guide_pdf = 0.0f;
|
||||
*sampled_roughness = 1.0f - fabsf(svc->g);
|
||||
|
||||
if (*pdf != 0.0f) {
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval);
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
|
||||
|
||||
if (sample_guiding) {
|
||||
/* Sample guiding distribution. */
|
||||
guide_pdf = guiding_phase_sample(kg, state, rand_phase, omega_in);
|
||||
*phase_pdf = 0.0f;
|
||||
|
||||
if (guide_pdf != 0.0f) {
|
||||
*unguided_phase_pdf = volume_shader_phase_eval(kg, sd, svc, *omega_in, phase_eval);
|
||||
*phase_pdf = (guiding_sampling_prob * guide_pdf) +
|
||||
((1.0f - guiding_sampling_prob) * (*unguided_phase_pdf));
|
||||
label = LABEL_VOLUME_SCATTER;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Sample phase. */
|
||||
*phase_pdf = 0.0f;
|
||||
label = volume_phase_sample(
|
||||
sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, unguided_phase_pdf);
|
||||
|
||||
if (*unguided_phase_pdf != 0.0f) {
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval);
|
||||
|
||||
*phase_pdf = *unguided_phase_pdf;
|
||||
if (use_volume_guiding) {
|
||||
guide_pdf = guiding_phase_pdf(kg, state, *omega_in);
|
||||
*phase_pdf *= 1.0f - guiding_sampling_prob;
|
||||
*phase_pdf += guiding_sampling_prob * guide_pdf;
|
||||
}
|
||||
|
||||
kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f);
|
||||
}
|
||||
else {
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum());
|
||||
}
|
||||
|
||||
kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
# endif
|
||||
|
||||
ccl_device int volume_shader_phase_sample_closure(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumeClosure *sc,
|
||||
const float2 rand_phase,
|
||||
ccl_private BsdfEval *phase_eval,
|
||||
ccl_private float3 *omega_in,
|
||||
ccl_private float *pdf)
|
||||
ccl_device int volume_shader_phase_sample(KernelGlobals kg,
|
||||
ccl_private const ShaderData *sd,
|
||||
ccl_private const ShaderVolumePhases *phases,
|
||||
ccl_private const ShaderVolumeClosure *svc,
|
||||
float2 rand_phase,
|
||||
ccl_private BsdfEval *phase_eval,
|
||||
ccl_private float3 *omega_in,
|
||||
ccl_private float *pdf,
|
||||
ccl_private float *sampled_roughness)
|
||||
{
|
||||
int label;
|
||||
*sampled_roughness = 1.0f - fabsf(svc->g);
|
||||
Spectrum eval = zero_spectrum();
|
||||
|
||||
*pdf = 0.0f;
|
||||
label = volume_phase_sample(sd, sc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf);
|
||||
int label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf);
|
||||
|
||||
if (*pdf != 0.0f)
|
||||
if (*pdf != 0.0f) {
|
||||
bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -49,6 +49,7 @@ set(SRC_HEADERS
|
||||
foreach.h
|
||||
function.h
|
||||
guarded_allocator.h
|
||||
guiding.h
|
||||
half.h
|
||||
hash.h
|
||||
ies.h
|
||||
|
41
intern/cycles/util/guiding.h
Normal file
41
intern/cycles/util/guiding.h
Normal file
@ -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
|
@ -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()
|
||||
|
@ -56,6 +56,8 @@ BLACKLIST_GPU = [
|
||||
# Inconsistent handling of overlapping objects.
|
||||
"T41143.blend",
|
||||
"visibility_particles.blend",
|
||||
# No path guiding on GPU.
|
||||
"guiding*.blend",
|
||||
]
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user