diff options
author | Sebastian Herhoz <sebastian.herholz@intel.com> | 2022-09-21 18:58:34 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2022-09-27 16:56:32 +0300 |
commit | 75a6d3abf75f3082adf5240ae34973844c0d9a09 (patch) | |
tree | 6967aea2480187db007cdc003bad14fbb372570d /intern/cycles/kernel/integrator | |
parent | 6d19da0b2d468f099e0c1f56392ab8a1750d114f (diff) |
Cycles: add Path Guiding on CPU through Intel OpenPGL
This adds path guiding features into Cycles by integrating Intel's Open Path
Guiding Library. It can be enabled in the Sampling > Path Guiding panel in the
render properties.
This feature helps reduce noise in scenes where finding a path to light is
difficult for regular path tracing.
The current implementation supports guiding directional sampling decisions on
surfaces, when the material contains a least one diffuse component, and in
volumes with isotropic and anisotropic Henyey-Greenstein phase functions.
On surfaces, the guided sampling decision is proportional to the product of
the incident radiance and the normal-oriented cosine lobe and in volumes it
is proportional to the product of the incident radiance and the phase function.
The incident radiance field of a scene is learned and updated during rendering
after each per-frame rendering iteration/progression.
At the moment, path guiding is only supported by the CPU backend. Support for
GPU backends will be added in future versions of OpenPGL.
Ref T92571
Differential Revision: https://developer.blender.org/D15286
Diffstat (limited to 'intern/cycles/kernel/integrator')
17 files changed, 1321 insertions, 105 deletions
diff --git a/intern/cycles/kernel/integrator/guiding.h b/intern/cycles/kernel/integrator/guiding.h new file mode 100644 index 00000000000..5d09e5ceac4 --- /dev/null +++ b/intern/cycles/kernel/integrator/guiding.h @@ -0,0 +1,542 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#pragma once + +#include "kernel/closure/alloc.h" +#include "kernel/closure/bsdf.h" +#include "kernel/film/write.h" + +CCL_NAMESPACE_BEGIN + +/* Utilities. */ + +#if defined(__PATH_GUIDING__) +static pgl_vec3f guiding_vec3f(const float3 v) +{ + return openpgl::cpp::Vector3(v.x, v.y, v.z); +} + +static pgl_point3f guiding_point3f(const float3 v) +{ + return openpgl::cpp::Point3(v.x, v.y, v.z); +} +#endif + +/* Path recording for guiding. */ + +/* Record Surface Interactions */ + +/* Records/Adds a new path segment with the current path vertex on a surface. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_surface_bounce. */ +ccl_device_forceinline void guiding_record_surface_segment(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(sd->P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(sd->I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the surface scattering event at the current vertex position of the segment.*/ +ccl_device_forceinline void guiding_record_surface_bounce(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + const Spectrum weight, + const float pdf, + const float3 N, + const float3 omega_in, + const float2 roughness, + const float eta) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float min_roughness = safe_sqrtf(fminf(roughness.x, roughness.y)); + const bool is_delta = (min_roughness == 0.0f); + const float3 weight_rgb = spectrum_to_rgb(weight); + const float3 normal = clamp(N, -one_float3(), one_float3()); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3())); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, is_delta); + openpgl::cpp::SetEta(state->guiding.path_segment, eta); + openpgl::cpp::SetRoughness(state->guiding.path_segment, min_roughness); +#endif +} + +/* Records the emission at the current surface intersection (physical or virtual) */ +ccl_device_forceinline void guiding_record_surface_emission(KernelGlobals kg, + IntegratorState state, + const Spectrum Le, + const float mis_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 Le_rgb = spectrum_to_rgb(Le); + + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb)); + openpgl::cpp::SetMiWeight(state->guiding.path_segment, mis_weight); +#endif +} + +/* Record BSSRDF Interactions */ + +/* Records/Adds a new path segment where the vertex position is the point of entry + * of the sub surface scattering boundary. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_bssrdf_weight and guiding_record_bssrdf_bounce. */ +ccl_device_forceinline void guiding_record_bssrdf_segment(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 I) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the transmission of the path at the point of entry while passing + * the surface boundary.*/ +ccl_device_forceinline void guiding_record_bssrdf_weight(KernelGlobals kg, + IntegratorState state, + const Spectrum weight, + const Spectrum albedo) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + /* Note albedo left out here, will be included in guiding_record_bssrdf_bounce. */ + const float3 weight_rgb = spectrum_to_rgb(safe_divide_color(weight, albedo)); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(zero_float3())); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, false); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f); + openpgl::cpp::SetRoughness(state->guiding.path_segment, 1.0f); +#endif +} + +/* Records the direction at the point of entry the path takes when sampling the SSS contribution. + * If not terminated this function is usually followed by a call of + * guiding_record_volume_transmission to record the transmittance between the point of entry and + * the point of exit.*/ +ccl_device_forceinline void guiding_record_bssrdf_bounce(KernelGlobals kg, + IntegratorState state, + const float pdf, + const float3 N, + const float3 omega_in, + const Spectrum weight, + const Spectrum albedo) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 normal = clamp(N, -one_float3(), one_float3()); + const float3 weight_rgb = spectrum_to_rgb(weight * albedo); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); +#endif +} + +/* Record Volume Interactions */ + +/* Records/Adds a new path segment with the current path vertex being inside a volume. + * If the path is not terminated this call is usually followed by a call of + * guiding_record_volume_bounce. */ +ccl_device_forceinline void guiding_record_volume_segment(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 I) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(I)); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0); +#endif +} + +/* Records the volume scattering event at the current vertex position of the segment.*/ +ccl_device_forceinline void guiding_record_volume_bounce(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + const Spectrum weight, + const float pdf, + const float3 omega_in, + const float roughness) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (!kernel_data.integrator.train_guiding) { + return; + } + const float3 weight_rgb = spectrum_to_rgb(weight); + const float3 normal = make_float3(0.0f, 0.0f, 1.0f); + + kernel_assert(state->guiding.path_segment != nullptr); + + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3())); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(omega_in)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb)); + openpgl::cpp::SetIsDelta(state->guiding.path_segment, false); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.f); + openpgl::cpp::SetRoughness(state->guiding.path_segment, roughness); +#endif +} + +/* Records the transmission (a.k.a. transmittance weight) between the current path segment + * and the next one, when the path is inside or passes a volume.*/ +ccl_device_forceinline void guiding_record_volume_transmission(KernelGlobals kg, + IntegratorState state, + const float3 transmittance_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + // TODO (sherholz): need to find a better way to avoid this check + if ((transmittance_weight[0] < 0.f || !std::isfinite(transmittance_weight[0]) || + std::isnan(transmittance_weight[0])) || + (transmittance_weight[1] < 0.f || !std::isfinite(transmittance_weight[1]) || + std::isnan(transmittance_weight[1])) || + (transmittance_weight[2] < 0.f || !std::isfinite(transmittance_weight[2]) || + std::isnan(transmittance_weight[2]))) { + } + else { + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, + guiding_vec3f(transmittance_weight)); + } + } +#endif +} + +/* Records the emission of a volume at the vertex of the current path segment. */ +ccl_device_forceinline void guiding_record_volume_emission(KernelGlobals kg, + IntegratorState state, + const Spectrum Le) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + const float3 Le_rgb = spectrum_to_rgb(Le); + + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb)); + openpgl::cpp::SetMiWeight(state->guiding.path_segment, 1.0f); + } +#endif +} + +/* Record Light Interactions */ + +/* Adds a pseudo path vertex/segment when intersecting a virtual light source. + * (e.g., area, sphere, or disk light). This call is often followed + * a call of guiding_record_surface_emission, if the intersected light source + * emits light in the direction of tha path. */ +ccl_device_forceinline void guiding_record_light_surface_segment( + KernelGlobals kg, IntegratorState state, ccl_private const Intersection *ccl_restrict isect) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + const pgl_vec3f zero = guiding_vec3f(zero_float3()); + const pgl_vec3f one = guiding_vec3f(one_float3()); + const float3 ray_P = INTEGRATOR_STATE(state, ray, P); + const float3 ray_D = INTEGRATOR_STATE(state, ray, D); + const float3 P = ray_P + isect->t * ray_D; + + state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment(); + openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(P)); + openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(ray_D)); + openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, 1.0f); + openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false); + openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero); + openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one); + openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, one); + openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f); +#endif +} + +/* Records/Adds a final path segment when the path leaves the scene and + * intersects with a background light (e.g., background color, + * distant light, or env map). The vertex for this segment is placed along + * the current ray far out the scene.*/ +ccl_device_forceinline void guiding_record_background(KernelGlobals kg, + IntegratorState state, + const Spectrum L, + const float mis_weight) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + const float3 L_rgb = spectrum_to_rgb(L); + const float3 ray_P = INTEGRATOR_STATE(state, ray, P); + const float3 ray_D = INTEGRATOR_STATE(state, ray, D); + const float3 P = ray_P + (1e6f) * ray_D; + const float3 normal = make_float3(0.0f, 0.0f, 1.0f); + + openpgl::cpp::PathSegment background_segment; + openpgl::cpp::SetPosition(&background_segment, guiding_vec3f(P)); + openpgl::cpp::SetNormal(&background_segment, guiding_vec3f(normal)); + openpgl::cpp::SetDirectionOut(&background_segment, guiding_vec3f(-ray_D)); + openpgl::cpp::SetDirectContribution(&background_segment, guiding_vec3f(L_rgb)); + openpgl::cpp::SetMiWeight(&background_segment, mis_weight); + kg->opgl_path_segment_storage->AddSegment(background_segment); +#endif +} + +/* Records the scattered contribution of a next event estimation + * (i.e., a direct light estimate scattered at the current path vertex + * towards the previous vertex).*/ +ccl_device_forceinline void guiding_record_direct_light(KernelGlobals kg, + IntegratorShadowState state) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + if (state->shadow_path.path_segment) { + const Spectrum Lo = safe_divide_color(INTEGRATOR_STATE(state, shadow_path, throughput), + INTEGRATOR_STATE(state, shadow_path, unlit_throughput)); + + const float3 Lo_rgb = spectrum_to_rgb(Lo); + openpgl::cpp::AddScatteredContribution(state->shadow_path.path_segment, guiding_vec3f(Lo_rgb)); + } +#endif +} + +/* Record Russian Roulette */ +/* Records the probability of continuing the path at the current path segment. */ +ccl_device_forceinline void guiding_record_continuation_probability( + KernelGlobals kg, IntegratorState state, const float continuation_probability) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (state->guiding.path_segment) { + openpgl::cpp::SetRussianRouletteProbability(state->guiding.path_segment, + continuation_probability); + } +#endif +} + +/* Path guiding debug render passes. */ + +/* Write a set of path guiding related debug information (e.g., guiding probability at first + * bounce) into separate rendering passes.*/ +ccl_device_forceinline void guiding_write_debug_passes(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + ccl_global float *ccl_restrict + render_buffer) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 +# ifdef WITH_CYCLES_DEBUG + if (!kernel_data.integrator.train_guiding) { + return; + } + + if (INTEGRATOR_STATE(state, path, bounce) != 0) { + return; + } + + const uint32_t render_pixel_index = INTEGRATOR_STATE(state, path, render_pixel_index); + const uint64_t render_buffer_offset = (uint64_t)render_pixel_index * + kernel_data.film.pass_stride; + ccl_global float *buffer = render_buffer + render_buffer_offset; + + if (kernel_data.film.pass_guiding_probability != PASS_UNUSED) { + float guiding_prob = state->guiding.surface_guiding_sampling_prob; + film_write_pass_float(buffer + kernel_data.film.pass_guiding_probability, guiding_prob); + } + + if (kernel_data.film.pass_guiding_avg_roughness != PASS_UNUSED) { + float avg_roughness = 0.0f; + float sum_sample_weight = 0.0f; + for (int i = 0; i < sd->num_closure; i++) { + ccl_private const ShaderClosure *sc = &sd->closure[i]; + + if (!CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + continue; + } + avg_roughness += sc->sample_weight * bsdf_get_specular_roughness_squared(sc); + sum_sample_weight += sc->sample_weight; + } + + avg_roughness = avg_roughness > 0.f ? avg_roughness / sum_sample_weight : 0.f; + + film_write_pass_float(buffer + kernel_data.film.pass_guiding_avg_roughness, avg_roughness); + } +# endif +#endif +} + +/* Guided BSDFs */ + +ccl_device_forceinline bool guiding_bsdf_init(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 N, + ccl_private float &rand) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kg->opgl_surface_sampling_distribution->Init( + kg->opgl_guiding_field, guiding_point3f(P), rand, true)) { + kg->opgl_surface_sampling_distribution->ApplyCosineProduct(guiding_point3f(N)); + return true; + } +#endif + + return false; +} + +ccl_device_forceinline float guiding_bsdf_sample(KernelGlobals kg, + IntegratorState state, + const float2 rand_bsdf, + ccl_private float3 *omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + pgl_vec3f wo; + const pgl_point2f rand = openpgl::cpp::Point2(rand_bsdf.x, rand_bsdf.y); + const float pdf = kg->opgl_surface_sampling_distribution->SamplePDF(rand, wo); + *omega_in = make_float3(wo.x, wo.y, wo.z); + return pdf; +#else + return 0.0f; +#endif +} + +ccl_device_forceinline float guiding_bsdf_pdf(KernelGlobals kg, + IntegratorState state, + const float3 omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + return kg->opgl_surface_sampling_distribution->PDF(guiding_vec3f(omega_in)); +#else + return 0.0f; +#endif +} + +/* Guided Volume Phases */ + +ccl_device_forceinline bool guiding_phase_init(KernelGlobals kg, + IntegratorState state, + const float3 P, + const float3 D, + const float g, + ccl_private float &rand) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kg->opgl_volume_sampling_distribution->Init( + kg->opgl_guiding_field, guiding_point3f(P), rand, true)) { + kg->opgl_volume_sampling_distribution->ApplySingleLobeHenyeyGreensteinProduct(guiding_vec3f(D), + g); + return true; + } +#endif + + return false; +} + +ccl_device_forceinline float guiding_phase_sample(KernelGlobals kg, + IntegratorState state, + const float2 rand_phase, + ccl_private float3 *omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + pgl_vec3f wo; + const pgl_point2f rand = openpgl::cpp::Point2(rand_phase.x, rand_phase.y); + const float pdf = kg->opgl_volume_sampling_distribution->SamplePDF(rand, wo); + *omega_in = make_float3(wo.x, wo.y, wo.z); + return pdf; +#else + return 0.0f; +#endif +} + +ccl_device_forceinline float guiding_phase_pdf(KernelGlobals kg, + IntegratorState state, + const float3 omega_in) +{ +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + return kg->opgl_volume_sampling_distribution->PDF(guiding_vec3f(omega_in)); +#else + return 0.0f; +#endif +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/intersect_closest.h b/intern/cycles/kernel/integrator/intersect_closest.h index c7c3d74fa21..b9a81e25bcc 100644 --- a/intern/cycles/kernel/integrator/intersect_closest.h +++ b/intern/cycles/kernel/integrator/intersect_closest.h @@ -7,6 +7,7 @@ #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/shadow_catcher.h" @@ -48,13 +49,15 @@ ccl_device_forceinline bool integrator_intersect_terminate(KernelGlobals kg, * surfaces in front of emission do we need to evaluate the shader, since we * perform MIS as part of indirect rays. */ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - const float probability = path_state_continuation_probability(kg, state, path_flag); - INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = probability; + const float continuation_probability = path_state_continuation_probability(kg, state, path_flag); + INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = continuation_probability; - if (probability != 1.0f) { + guiding_record_continuation_probability(kg, state, continuation_probability); + + if (continuation_probability != 1.0f) { const float terminate = path_state_rng_1D(kg, &rng_state, PRNG_TERMINATE); - if (probability == 0.0f || terminate >= probability) { + if (continuation_probability == 0.0f || terminate >= continuation_probability) { if (shader_flags & SD_HAS_EMISSION) { /* Mark path to be terminated right after shader evaluation on the surface. */ INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE; diff --git a/intern/cycles/kernel/integrator/mnee.h b/intern/cycles/kernel/integrator/mnee.h index a39209bc937..038f0379bbc 100644 --- a/intern/cycles/kernel/integrator/mnee.h +++ b/intern/cycles/kernel/integrator/mnee.h @@ -807,7 +807,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg, float3 wo = normalize_len(vertices[0].p - sd->P, &wo_len); /* Initialize throughput and evaluate receiver bsdf * |n.wo|. */ - surface_shader_bsdf_eval(kg, sd, wo, throughput, ls->shader); + surface_shader_bsdf_eval(kg, state, sd, wo, throughput, ls->shader); /* Update light sample with new position / direct.ion * and keep pdf in vertex area measure */ diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 54560905397..dbc6fc5a883 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -56,6 +56,11 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = 1.0f; INTEGRATOR_STATE_WRITE(state, path, throughput) = one_spectrum(); +#ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) = 1.0f; + INTEGRATOR_STATE_WRITE(state, guiding, path_segment) = nullptr; +#endif + #ifdef __MNEE__ INTEGRATOR_STATE_WRITE(state, path, mnee) = 0; #endif @@ -249,7 +254,11 @@ ccl_device_inline float path_state_continuation_probability(KernelGlobals kg, /* Probabilistic termination: use sqrt() to roughly match typical view * transform and do path termination a bit later on average. */ - return min(sqrtf(reduce_max(fabs(INTEGRATOR_STATE(state, path, throughput)))), 1.0f); + Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + throughput *= INTEGRATOR_STATE(state, path, unguided_throughput); +#endif + return min(sqrtf(reduce_max(fabs(throughput))), 1.0f); } ccl_device_inline bool path_state_ao_bounce(KernelGlobals kg, ConstIntegratorState state) diff --git a/intern/cycles/kernel/integrator/shade_background.h b/intern/cycles/kernel/integrator/shade_background.h index 30ce0999258..8fc5689683a 100644 --- a/intern/cycles/kernel/integrator/shade_background.h +++ b/intern/cycles/kernel/integrator/shade_background.h @@ -5,6 +5,7 @@ #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/surface_shader.h" #include "kernel/light/light.h" @@ -124,6 +125,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg, mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf); } + guiding_record_background(kg, state, L, mis_weight); L *= mis_weight; } @@ -185,6 +187,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg, } /* Write to render buffer. */ + guiding_record_background(kg, state, light_eval, mis_weight); film_write_surface_emission( kg, state, light_eval, mis_weight, render_buffer, kernel_data.background.lightgroup); } diff --git a/intern/cycles/kernel/integrator/shade_light.h b/intern/cycles/kernel/integrator/shade_light.h index f2d65eddfbb..e0b0500dc78 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -18,6 +18,8 @@ ccl_device_inline void integrate_light(KernelGlobals kg, Intersection isect ccl_optional_struct_init; integrator_state_read_isect(kg, state, &isect); + guiding_record_light_surface_segment(kg, state, &isect); + float3 ray_P = INTEGRATOR_STATE(state, ray, P); const float3 ray_D = INTEGRATOR_STATE(state, ray, D); const float ray_time = INTEGRATOR_STATE(state, ray, time); @@ -66,6 +68,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg, } /* Write to render buffer. */ + guiding_record_surface_emission(kg, state, light_eval, mis_weight); film_write_surface_emission(kg, state, light_eval, mis_weight, render_buffer, ls.group); } diff --git a/intern/cycles/kernel/integrator/shade_shadow.h b/intern/cycles/kernel/integrator/shade_shadow.h index ba18aed6ff0..bedb15ddf89 100644 --- a/intern/cycles/kernel/integrator/shade_shadow.h +++ b/intern/cycles/kernel/integrator/shade_shadow.h @@ -3,6 +3,7 @@ #pragma once +#include "kernel/integrator/guiding.h" #include "kernel/integrator/shade_volume.h" #include "kernel/integrator/surface_shader.h" #include "kernel/integrator/volume_stack.h" @@ -165,6 +166,7 @@ ccl_device void integrator_shade_shadow(KernelGlobals kg, return; } else { + guiding_record_direct_light(kg, state); film_write_direct_light(kg, state, render_buffer); integrator_shadow_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index c766ea2a07e..067d35ef9e3 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -9,6 +9,7 @@ #include "kernel/integrator/mnee.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/subsurface.h" #include "kernel/integrator/surface_shader.h" @@ -101,7 +102,7 @@ ccl_device_forceinline bool integrate_surface_holdout(KernelGlobals kg, } ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, - ConstIntegratorState state, + IntegratorState state, ccl_private const ShaderData *sd, ccl_global float *ccl_restrict render_buffer) @@ -128,6 +129,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf); } + guiding_record_surface_emission(kg, state, L, mis_weight); film_write_surface_emission( kg, state, L, mis_weight, render_buffer, object_lightgroup(kg, sd->object)); } @@ -210,7 +212,7 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, } /* Evaluate BSDF. */ - const float bsdf_pdf = surface_shader_bsdf_eval(kg, sd, ls.D, &bsdf_eval, ls.shader); + const float bsdf_pdf = surface_shader_bsdf_eval(kg, state, sd, ls.D, &bsdf_eval, ls.shader); bsdf_eval_mul(&bsdf_eval, light_eval / ls.pdf); if (ls.shader & SHADER_USE_MIS) { @@ -258,8 +260,8 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, /* Copy state from main path to shadow path. */ uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag); shadow_flag |= (is_light) ? PATH_RAY_SHADOW_FOR_LIGHT : 0; - const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput) * - bsdf_eval_sum(&bsdf_eval); + const Spectrum unlit_throughput = INTEGRATOR_STATE(state, path, throughput); + const Spectrum throughput = unlit_throughput * bsdf_eval_sum(&bsdf_eval); if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { PackedSpectrum pass_diffuse_weight; @@ -329,6 +331,11 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ? ls.group + 1 : kernel_data.background.lightgroup + 1; +#ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE( + state, guiding, path_segment); +#endif } /* Path tracing: bounce off or through surface with new direction. */ @@ -354,7 +361,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( #endif /* BSDF closure, sample direction. */ - float bsdf_pdf; + float bsdf_pdf = 0.0f, unguided_bsdf_pdf = 0.0f; BsdfEval bsdf_eval ccl_optional_struct_init; float3 bsdf_omega_in ccl_optional_struct_init; int label; @@ -362,18 +369,44 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( float2 bsdf_sampled_roughness = make_float2(1.0f, 1.0f); float bsdf_eta = 1.0f; - label = surface_shader_bsdf_sample_closure(kg, - sd, - sc, - rand_bsdf, - &bsdf_eval, - &bsdf_omega_in, - &bsdf_pdf, - &bsdf_sampled_roughness, - &bsdf_eta); - - if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { - return LABEL_NONE; +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kernel_data.integrator.use_surface_guiding) { + label = surface_shader_bsdf_guided_sample_closure(kg, + state, + sd, + sc, + rand_bsdf, + &bsdf_eval, + &bsdf_omega_in, + &bsdf_pdf, + &unguided_bsdf_pdf, + &bsdf_sampled_roughness, + &bsdf_eta); + + if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { + return LABEL_NONE; + } + + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= bsdf_pdf / unguided_bsdf_pdf; + } + else +#endif + { + label = surface_shader_bsdf_sample_closure(kg, + sd, + sc, + rand_bsdf, + &bsdf_eval, + &bsdf_omega_in, + &bsdf_pdf, + &bsdf_sampled_roughness, + &bsdf_eta); + + if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) { + return LABEL_NONE; + } + + unguided_bsdf_pdf = bsdf_pdf; } if (label & LABEL_TRANSPARENT) { @@ -393,9 +426,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( } /* Update throughput. */ - Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); - throughput *= bsdf_eval_sum(&bsdf_eval) / bsdf_pdf; - INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput; + const Spectrum bsdf_weight = bsdf_eval_sum(&bsdf_eval) / bsdf_pdf; + INTEGRATOR_STATE_WRITE(state, path, throughput) *= bsdf_weight; if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { if (INTEGRATOR_STATE(state, path, bounce) == 0) { @@ -410,10 +442,21 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( if (!(label & LABEL_TRANSPARENT)) { INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( - bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); + unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); } path_state_next(kg, state, label); + + guiding_record_surface_bounce(kg, + state, + sd, + bsdf_weight, + bsdf_pdf, + sd->N, + normalize(bsdf_omega_in), + bsdf_sampled_roughness, + bsdf_eta); + return label; } @@ -435,14 +478,15 @@ ccl_device_forceinline int integrate_surface_volume_only_bounce(IntegratorState ccl_device_forceinline bool integrate_surface_terminate(IntegratorState state, const uint32_t path_flag) { - const float probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? - 0.0f : - INTEGRATOR_STATE(state, path, continuation_probability); - if (probability == 0.0f) { + const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? + 0.0f : + INTEGRATOR_STATE( + state, path, continuation_probability); + if (continuation_probability == 0.0f) { return true; } - else if (probability != 1.0f) { - INTEGRATOR_STATE_WRITE(state, path, throughput) /= probability; + else if (continuation_probability != 1.0f) { + INTEGRATOR_STATE_WRITE(state, path, throughput) /= continuation_probability; } return false; @@ -550,6 +594,8 @@ ccl_device bool integrate_surface(KernelGlobals kg, #ifdef __VOLUME__ if (!(sd.flag & SD_HAS_ONLY_VOLUME)) { #endif + guiding_record_surface_segment(kg, state, &sd); + #ifdef __SUBSURFACE__ /* Can skip shader evaluation for BSSRDF exit point without bump mapping. */ if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP))) @@ -615,6 +661,10 @@ ccl_device bool integrate_surface(KernelGlobals kg, RNGState rng_state; path_state_rng_load(state, &rng_state); +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + surface_shader_prepare_guiding(kg, state, &sd, &rng_state); + guiding_write_debug_passes(kg, state, &sd, render_buffer); +#endif /* Direct light. */ PROFILING_EVENT(PROFILING_SHADE_SURFACE_DIRECT_LIGHT); integrate_surface_direct_light<node_feature_mask>(kg, state, &sd, &rng_state); diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index aaef92729d6..a8324cda2dc 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -7,6 +7,7 @@ #include "kernel/film/denoising_passes.h" #include "kernel/film/light_passes.h" +#include "kernel/integrator/guiding.h" #include "kernel/integrator/intersect_closest.h" #include "kernel/integrator/path_state.h" #include "kernel/integrator/volume_shader.h" @@ -612,6 +613,7 @@ ccl_device_forceinline void volume_integrate_heterogeneous( const Spectrum emission = volume_emission_integrate( &coeff, closure_flag, transmittance, dt); accum_emission += result.indirect_throughput * emission; + guiding_record_volume_emission(kg, state, emission); } } @@ -761,7 +763,7 @@ ccl_device_forceinline void integrate_volume_direct_light( /* Evaluate BSDF. */ BsdfEval phase_eval ccl_optional_struct_init; - const float phase_pdf = volume_shader_phase_eval(kg, sd, phases, ls->D, &phase_eval); + float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval); if (ls->shader & SHADER_USE_MIS) { float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf); @@ -848,6 +850,12 @@ ccl_device_forceinline void integrate_volume_direct_light( ls->group + 1 : kernel_data.background.lightgroup + 1; +# ifdef __PATH_GUIDING__ + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE( + state, guiding, path_segment); +# endif + integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state); } @@ -861,18 +869,54 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( { PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_INDIRECT_LIGHT); - const float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE); + float2 rand_phase = path_state_rng_2D(kg, rng_state, PRNG_VOLUME_PHASE); + + ccl_private const ShaderVolumeClosure *svc = volume_shader_phase_pick(phases, &rand_phase); /* Phase closure, sample direction. */ - float phase_pdf; + float phase_pdf = 0.0f, unguided_phase_pdf = 0.0f; BsdfEval phase_eval ccl_optional_struct_init; float3 phase_omega_in ccl_optional_struct_init; + float sampled_roughness = 1.0f; + int label; + +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (kernel_data.integrator.use_guiding) { + label = volume_shader_phase_guided_sample(kg, + state, + sd, + svc, + rand_phase, + &phase_eval, + &phase_omega_in, + &phase_pdf, + &unguided_phase_pdf, + &sampled_roughness); + + if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { + return false; + } - const int label = volume_shader_phase_sample( - kg, sd, phases, rand_phase, &phase_eval, &phase_omega_in, &phase_pdf); + INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= phase_pdf / unguided_phase_pdf; + } + else +# endif + { + label = volume_shader_phase_sample(kg, + sd, + phases, + svc, + rand_phase, + &phase_eval, + &phase_omega_in, + &phase_pdf, + &sampled_roughness); + + if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { + return false; + } - if (phase_pdf == 0.0f || bsdf_eval_is_zero(&phase_eval)) { - return false; + unguided_phase_pdf = phase_pdf; } /* Setup ray. */ @@ -887,9 +931,15 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( INTEGRATOR_STATE_WRITE(state, isect, prim) = sd->prim; INTEGRATOR_STATE_WRITE(state, isect, object) = sd->object; + const Spectrum phase_weight = bsdf_eval_sum(&phase_eval) / phase_pdf; + + /* Add phase function sampling data to the path segment. */ + guiding_record_volume_bounce( + kg, state, sd, phase_weight, phase_pdf, normalize(phase_omega_in), sampled_roughness); + /* Update throughput. */ const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); - const Spectrum throughput_phase = throughput * bsdf_eval_sum(&phase_eval) / phase_pdf; + const Spectrum throughput_phase = throughput * phase_weight; INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput_phase; if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) { @@ -900,7 +950,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( /* Update path state */ INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( - phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); + unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); path_state_next(kg, state, label); return true; @@ -939,6 +989,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, VOLUME_READ_LAMBDA(integrator_state_read_volume_stack(state, i)) const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass); +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput); +# endif + /* TODO: expensive to zero closures? */ VolumeIntegrateResult result = {}; volume_integrate_heterogeneous(kg, @@ -956,17 +1010,50 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, * to be terminated. That will shading evaluating to leave out any scattering closures, * but emission and absorption are still handled for multiple importance sampling. */ const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag); - const float probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ? - 0.0f : - INTEGRATOR_STATE(state, path, continuation_probability); - if (probability == 0.0f) { + const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_IN_NEXT_VOLUME) ? + 0.0f : + INTEGRATOR_STATE( + state, path, continuation_probability); + if (continuation_probability == 0.0f) { return VOLUME_PATH_MISSED; } +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + bool guiding_generated_new_segment = false; + if (kernel_data.integrator.use_guiding) { + /* Record transmittance using change in throughput. */ + float3 transmittance_weight = spectrum_to_rgb( + safe_divide_color(result.indirect_throughput, initial_throughput)); + guiding_record_volume_transmission(kg, state, transmittance_weight); + + if (result.indirect_scatter) { + const float3 P = ray->P + result.indirect_t * ray->D; + + /* Record volume segment up to direct scatter position. + * TODO: volume segment is wrong when direct_t and indirect_t. */ + if (result.direct_scatter && (result.direct_t == result.indirect_t)) { + guiding_record_volume_segment(kg, state, P, sd.I); + guiding_generated_new_segment = true; + } + +# if PATH_GUIDING_LEVEL >= 4 + /* TODO: this position will be wrong for direct light pdf computation, + * since the direct light position may be different? */ + volume_shader_prepare_guiding( + kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method); +# endif + } + else { + /* No guiding if we don't scatter. */ + state->guiding.use_volume_guiding = false; + } + } +# endif + /* Direct light. */ if (result.direct_scatter) { const float3 direct_P = ray->P + result.direct_t * ray->D; - result.direct_throughput /= probability; + result.direct_throughput /= continuation_probability; integrate_volume_direct_light(kg, state, &sd, @@ -979,16 +1066,22 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg, /* Indirect light. * - * Only divide throughput by probability if we scatter. For the attenuation + * Only divide throughput by continuation_probability if we scatter. For the attenuation * case the next surface will already do this division. */ if (result.indirect_scatter) { - result.indirect_throughput /= probability; + result.indirect_throughput /= continuation_probability; } INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput; if (result.indirect_scatter) { sd.P = ray->P + result.indirect_t * ray->D; +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1 + if (!guiding_generated_new_segment) { + guiding_record_volume_segment(kg, state, sd.P, sd.I); + } +# endif + if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) { return VOLUME_PATH_SCATTERED; } diff --git a/intern/cycles/kernel/integrator/shadow_state_template.h b/intern/cycles/kernel/integrator/shadow_state_template.h index 3b490ecffdd..d731d1df339 100644 --- a/intern/cycles/kernel/integrator/shadow_state_template.h +++ b/intern/cycles/kernel/integrator/shadow_state_template.h @@ -40,6 +40,16 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEA KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, num_hits, KERNEL_FEATURE_PATH_TRACING) /* Light group. */ KERNEL_STRUCT_MEMBER(shadow_path, uint8_t, lightgroup, KERNEL_FEATURE_PATH_TRACING) +/* Path guiding. */ +KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, unlit_throughput, KERNEL_FEATURE_PATH_GUIDING) +#ifdef __PATH_GUIDING__ +KERNEL_STRUCT_MEMBER(shadow_path, + openpgl::cpp::PathSegment *, + path_segment, + KERNEL_FEATURE_PATH_GUIDING) +#else +KERNEL_STRUCT_MEMBER(shadow_path, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING) +#endif KERNEL_STRUCT_END(shadow_path) /********************************** Shadow Ray *******************************/ diff --git a/intern/cycles/kernel/integrator/state.h b/intern/cycles/kernel/integrator/state.h index d1907bd6e16..f0fdc6f0d54 100644 --- a/intern/cycles/kernel/integrator/state.h +++ b/intern/cycles/kernel/integrator/state.h @@ -31,6 +31,10 @@ #include "util/types.h" +#ifdef __PATH_GUIDING__ +# include "util/guiding.h" +#endif + #pragma once CCL_NAMESPACE_BEGIN diff --git a/intern/cycles/kernel/integrator/state_template.h b/intern/cycles/kernel/integrator/state_template.h index f4e280e4cb2..760c2f80521 100644 --- a/intern/cycles/kernel/integrator/state_template.h +++ b/intern/cycles/kernel/integrator/state_template.h @@ -47,6 +47,9 @@ KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(path, float, continuation_probability, KERNEL_FEATURE_PATH_TRACING) /* Throughput. */ KERNEL_STRUCT_MEMBER(path, PackedSpectrum, throughput, KERNEL_FEATURE_PATH_TRACING) +/* Factor to multiple with throughput to get remove any guiding pdfs. + * Such throughput without guiding pdfs is used for Russian rouletter termination. */ +KERNEL_STRUCT_MEMBER(path, float, unguided_throughput, KERNEL_FEATURE_PATH_GUIDING) /* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */ KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES) KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES) @@ -98,3 +101,33 @@ KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, shader, KERNEL_FEATURE_VOLUME) KERNEL_STRUCT_END_ARRAY(volume_stack, KERNEL_STRUCT_VOLUME_STACK_SIZE, KERNEL_STRUCT_VOLUME_STACK_SIZE) + +/************************************ Path Guiding *****************************/ +KERNEL_STRUCT_BEGIN(guiding) +#ifdef __PATH_GUIDING__ +/* Current path segment of the random walk/path. */ +KERNEL_STRUCT_MEMBER(guiding, + openpgl::cpp::PathSegment *, + path_segment, + KERNEL_FEATURE_PATH_GUIDING) +#else +/* Current path segment of the random walk/path. */ +KERNEL_STRUCT_MEMBER(guiding, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING) +#endif +/* If surface guiding is enabled */ +KERNEL_STRUCT_MEMBER(guiding, bool, use_surface_guiding, KERNEL_FEATURE_PATH_GUIDING) +/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding + * or bsdf sampling) */ +KERNEL_STRUCT_MEMBER(guiding, float, sample_surface_guiding_rand, KERNEL_FEATURE_PATH_GUIDING) +/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob)*/ +KERNEL_STRUCT_MEMBER(guiding, float, surface_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +/* Probability of sampling a bssrdf closure instead of a bsdf closure*/ +KERNEL_STRUCT_MEMBER(guiding, float, bssrdf_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +/* If volume guiding is enabled */ +KERNEL_STRUCT_MEMBER(guiding, bool, use_volume_guiding, KERNEL_FEATURE_PATH_GUIDING) +/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding + * or bsdf sampling) */ +KERNEL_STRUCT_MEMBER(guiding, float, sample_volume_guiding_rand, KERNEL_FEATURE_PATH_GUIDING) +/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob)*/ +KERNEL_STRUCT_MEMBER(guiding, float, volume_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING) +KERNEL_STRUCT_END(guiding) diff --git a/intern/cycles/kernel/integrator/subsurface.h b/intern/cycles/kernel/integrator/subsurface.h index 15c2cb1c708..efd293e4141 100644 --- a/intern/cycles/kernel/integrator/subsurface.h +++ b/intern/cycles/kernel/integrator/subsurface.h @@ -78,6 +78,9 @@ ccl_device int subsurface_bounce(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, subsurface, radius) = bssrdf->radius; INTEGRATOR_STATE_WRITE(state, subsurface, anisotropy) = bssrdf->anisotropy; + /* Path guiding. */ + guiding_record_bssrdf_weight(kg, state, weight, bssrdf->albedo); + return LABEL_SUBSURFACE_SCATTER; } diff --git a/intern/cycles/kernel/integrator/subsurface_disk.h b/intern/cycles/kernel/integrator/subsurface_disk.h index a44b6a74d7b..16fb45392f4 100644 --- a/intern/cycles/kernel/integrator/subsurface_disk.h +++ b/intern/cycles/kernel/integrator/subsurface_disk.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ +#include "kernel/integrator/guiding.h" + CCL_NAMESPACE_BEGIN /* BSSRDF using disk based importance sampling. @@ -173,8 +175,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, if (r < next_sum) { /* Return exit point. */ - INTEGRATOR_STATE_WRITE(state, path, throughput) *= weight * sum_weights / sample_weight; - + const Spectrum resampled_weight = weight * sum_weights / sample_weight; + INTEGRATOR_STATE_WRITE(state, path, throughput) *= resampled_weight; ss_isect.hits[0] = ss_isect.hits[hit]; ss_isect.Ng[0] = ss_isect.Ng[hit]; @@ -182,6 +184,9 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, ray.D = ss_isect.Ng[hit]; ray.tmin = 0.0f; ray.tmax = 1.0f; + + guiding_record_bssrdf_bounce( + kg, state, 1.0f, Ng, -Ng, resampled_weight, INTEGRATOR_STATE(state, subsurface, albedo)); return true; } diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index a6a59e286c9..fdcb66c32f5 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -5,6 +5,8 @@ #include "kernel/bvh/bvh.h" +#include "kernel/integrator/guiding.h" + CCL_NAMESPACE_BEGIN /* Random walk subsurface scattering. @@ -203,7 +205,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, const float anisotropy = INTEGRATOR_STATE(state, subsurface, anisotropy); Spectrum sigma_t, alpha; - Spectrum throughput = INTEGRATOR_STATE_WRITE(state, path, throughput); + Spectrum throughput = INTEGRATOR_STATE(state, path, throughput); subsurface_random_walk_coefficients(albedo, radius, anisotropy, &sigma_t, &alpha, &throughput); Spectrum sigma_s = sigma_t * alpha; @@ -350,7 +352,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, } } - /* Sample direction along ray. */ + /* Sample distance along ray. */ float t = -logf(1.0f - randt) / sample_sigma_t; /* On the first bounce, we use the ray-cast to check if the opposite side is nearby. @@ -432,6 +434,16 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, if (hit) { kernel_assert(isfinite_safe(throughput)); + + guiding_record_bssrdf_bounce( + kg, + state, + pdf, + N, + D, + safe_divide_color(throughput, INTEGRATOR_STATE(state, path, throughput)), + albedo); + INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput; } diff --git a/intern/cycles/kernel/integrator/surface_shader.h b/intern/cycles/kernel/integrator/surface_shader.h index 7da81984a12..6c0097b11bd 100644 --- a/intern/cycles/kernel/integrator/surface_shader.h +++ b/intern/cycles/kernel/integrator/surface_shader.h @@ -10,6 +10,8 @@ #include "kernel/closure/bsdf_util.h" #include "kernel/closure/emissive.h" +#include "kernel/integrator/guiding.h" + #ifdef __SVM__ # include "kernel/svm/svm.h" #endif @@ -19,6 +21,67 @@ CCL_NAMESPACE_BEGIN +/* Guiding */ + +#ifdef __PATH_GUIDING__ +ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const RNGState *rng_state) +{ + /* Have any BSDF to guide? */ + if (!(kernel_data.integrator.use_surface_guiding && (sd->flag & SD_BSDF_HAS_EVAL))) { + state->guiding.use_surface_guiding = false; + return; + } + + const float surface_guiding_probability = kernel_data.integrator.surface_guiding_probability; + float rand_bsdf_guiding = path_state_rng_1D(kg, rng_state, PRNG_SURFACE_BSDF_GUIDING); + + /* Compute proportion of diffuse BSDF and BSSRDFs .*/ + float diffuse_sampling_fraction = 0.0f; + float bssrdf_sampling_fraction = 0.0f; + float bsdf_bssrdf_sampling_sum = 0.0f; + + for (int i = 0; i < sd->num_closure; i++) { + ShaderClosure *sc = &sd->closure[i]; + if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + const float sweight = sc->sample_weight; + kernel_assert(sweight >= 0.0f); + + bsdf_bssrdf_sampling_sum += sweight; + if (CLOSURE_IS_BSDF_DIFFUSE(sc->type) && sc->type < CLOSURE_BSDF_TRANSLUCENT_ID) { + diffuse_sampling_fraction += sweight; + } + if (CLOSURE_IS_BSSRDF(sc->type)) { + bssrdf_sampling_fraction += sweight; + } + } + } + + if (bsdf_bssrdf_sampling_sum > 0.0f) { + diffuse_sampling_fraction /= bsdf_bssrdf_sampling_sum; + bssrdf_sampling_fraction /= bsdf_bssrdf_sampling_sum; + } + + /* Init guiding (diffuse BSDFs only for now). */ + if (!(diffuse_sampling_fraction > 0.0f && + guiding_bsdf_init(kg, state, sd->P, sd->N, rand_bsdf_guiding))) { + state->guiding.use_surface_guiding = false; + return; + } + + state->guiding.use_surface_guiding = true; + state->guiding.surface_guiding_sampling_prob = surface_guiding_probability * + diffuse_sampling_fraction; + state->guiding.bssrdf_sampling_prob = bssrdf_sampling_fraction; + state->guiding.sample_surface_guiding_rand = rand_bsdf_guiding; + + kernel_assert(state->guiding.surface_guiding_sampling_prob > 0.0f && + state->guiding.surface_guiding_sampling_prob <= 1.0f); +} +#endif + ccl_device_inline void surface_shader_prepare_closures(KernelGlobals kg, ConstIntegratorState state, ccl_private ShaderData *sd, @@ -108,6 +171,28 @@ ccl_device_inline void surface_shader_prepare_closures(KernelGlobals kg, } /* BSDF */ +#if 0 +ccl_device_inline void surface_shader_validate_bsdf_sample(const KernelGlobals kg, + const ShaderClosure *sc, + const float3 omega_in, + const int org_label, + const float2 org_roughness, + const float org_eta) +{ + /* Validate the the bsdf_label and bsdf_roughness_eta functions + * by estimating the values after a bsdf sample. */ + const int comp_label = bsdf_label(kg, sc, omega_in); + kernel_assert(org_label == comp_label); + + float2 comp_roughness; + float comp_eta; + bsdf_roughness_eta(kg, sc, &comp_roughness, &comp_eta); + kernel_assert(org_eta == comp_eta); + kernel_assert(org_roughness.x == comp_roughness.x); + kernel_assert(org_roughness.y == comp_roughness.y); +} +#endif + ccl_device_forceinline bool _surface_shader_exclude(ClosureType type, uint light_shader_flags) { if (!(light_shader_flags & SHADER_EXCLUDE_ANY)) { @@ -167,6 +252,55 @@ ccl_device_inline float _surface_shader_bsdf_eval_mis(KernelGlobals kg, return (sum_sample_weight > 0.0f) ? sum_pdf / sum_sample_weight : 0.0f; } +ccl_device_inline float surface_shader_bsdf_eval_pdfs(const KernelGlobals kg, + ccl_private ShaderData *sd, + const float3 omega_in, + ccl_private BsdfEval *result_eval, + ccl_private float *pdfs, + const uint light_shader_flags) +{ + /* This is the veach one-sample model with balance heuristic, some pdf + * factors drop out when using balance heuristic weighting. */ + float sum_pdf = 0.0f; + float sum_sample_weight = 0.0f; + bsdf_eval_init(result_eval, CLOSURE_NONE_ID, zero_spectrum()); + for (int i = 0; i < sd->num_closure; i++) { + ccl_private const ShaderClosure *sc = &sd->closure[i]; + + if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) { + if (CLOSURE_IS_BSDF(sc->type) && !_surface_shader_exclude(sc->type, light_shader_flags)) { + float bsdf_pdf = 0.0f; + Spectrum eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf); + kernel_assert(bsdf_pdf >= 0.0f); + if (bsdf_pdf != 0.0f) { + bsdf_eval_accum(result_eval, sc->type, eval * sc->weight); + sum_pdf += bsdf_pdf * sc->sample_weight; + kernel_assert(bsdf_pdf * sc->sample_weight >= 0.0f); + pdfs[i] = bsdf_pdf * sc->sample_weight; + } + else { + pdfs[i] = 0.0f; + } + } + else { + pdfs[i] = 0.0f; + } + + sum_sample_weight += sc->sample_weight; + } + else { + pdfs[i] = 0.0f; + } + } + if (sum_pdf > 0.0f) { + for (int i = 0; i < sd->num_closure; i++) { + pdfs[i] /= sum_pdf; + } + } + + return (sum_sample_weight > 0.0f) ? sum_pdf / sum_sample_weight : 0.0f; +} + #ifndef __KERNEL_CUDA__ ccl_device #else @@ -174,6 +308,7 @@ ccl_device_inline #endif float surface_shader_bsdf_eval(KernelGlobals kg, + IntegratorState state, ccl_private ShaderData *sd, const float3 omega_in, ccl_private BsdfEval *bsdf_eval, @@ -181,8 +316,20 @@ ccl_device_inline { bsdf_eval_init(bsdf_eval, CLOSURE_NONE_ID, zero_spectrum()); - return _surface_shader_bsdf_eval_mis( + float pdf = _surface_shader_bsdf_eval_mis( kg, sd, omega_in, NULL, bsdf_eval, 0.0f, 0.0f, light_shader_flags); + +#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (state->guiding.use_surface_guiding) { + const float guiding_sampling_prob = state->guiding.surface_guiding_sampling_prob; + const float bssrdf_sampling_prob = state->guiding.bssrdf_sampling_prob; + const float guide_pdf = guiding_bsdf_pdf(kg, state, omega_in); + pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) + + (1.0f - guiding_sampling_prob) * pdf; + } +#endif + + return pdf; } /* Randomly sample a BSSRDF or BSDF proportional to ShaderClosure.sample_weight. */ @@ -250,6 +397,135 @@ surface_shader_bssrdf_sample_weight(ccl_private const ShaderData *ccl_restrict s return weight; } +#ifdef __PATH_GUIDING__ +/* Sample direction for picked BSDF, and return evaluation and pdf for all + * BSDFs combined using MIS. */ + +ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const ShaderClosure *sc, + const float2 rand_bsdf, + ccl_private BsdfEval *bsdf_eval, + ccl_private float3 *omega_in, + ccl_private float *bsdf_pdf, + ccl_private float *unguided_bsdf_pdf, + ccl_private float2 *sampled_rougness, + ccl_private float *eta) +{ + /* BSSRDF should already have been handled elsewhere. */ + kernel_assert(CLOSURE_IS_BSDF(sc->type)); + + const bool use_surface_guiding = state->guiding.use_surface_guiding; + const float guiding_sampling_prob = state->guiding.surface_guiding_sampling_prob; + const float bssrdf_sampling_prob = state->guiding.bssrdf_sampling_prob; + + /* Decide between sampling guiding distribution and BSDF. */ + bool sample_guiding = false; + float rand_bsdf_guiding = state->guiding.sample_surface_guiding_rand; + + if (use_surface_guiding && rand_bsdf_guiding < guiding_sampling_prob) { + sample_guiding = true; + rand_bsdf_guiding /= guiding_sampling_prob; + } + else { + rand_bsdf_guiding -= guiding_sampling_prob; + rand_bsdf_guiding /= (1.0f - guiding_sampling_prob); + } + + /* Initialize to zero. */ + int label = LABEL_NONE; + Spectrum eval = zero_spectrum(); + bsdf_eval_init(bsdf_eval, CLOSURE_NONE_ID, eval); + + *unguided_bsdf_pdf = 0.0f; + float guide_pdf = 0.0f; + + if (sample_guiding) { + /* Sample guiding distribution. */ + guide_pdf = guiding_bsdf_sample(kg, state, rand_bsdf, omega_in); + *bsdf_pdf = 0.0f; + + if (guide_pdf != 0.0f) { + float unguided_bsdf_pdfs[MAX_CLOSURE]; + + *unguided_bsdf_pdf = surface_shader_bsdf_eval_pdfs( + kg, sd, *omega_in, bsdf_eval, unguided_bsdf_pdfs, 0); + *bsdf_pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) + + ((1.0f - guiding_sampling_prob) * (*unguided_bsdf_pdf)); + float sum_pdfs = 0.0f; + + if (*unguided_bsdf_pdf > 0.0f) { + int idx = -1; + for (int i = 0; i < sd->num_closure; i++) { + sum_pdfs += unguided_bsdf_pdfs[i]; + if (rand_bsdf_guiding <= sum_pdfs) { + idx = i; + break; + } + } + + kernel_assert(idx >= 0); + /* Set the default idx to the last in the list. + * in case of numerical problems and rand_bsdf_guiding is just >=1.0f and + * the sum of all unguided_bsdf_pdfs is just < 1.0f. */ + idx = (rand_bsdf_guiding > sum_pdfs) ? sd->num_closure - 1 : idx; + + label = bsdf_label(kg, &sd->closure[idx], *omega_in); + } + } + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + + *sampled_rougness = make_float2(1.0f, 1.0f); + *eta = 1.0f; + } + else { + /* Sample BSDF. */ + *bsdf_pdf = 0.0f; + label = bsdf_sample(kg, + sd, + sc, + rand_bsdf.x, + rand_bsdf.y, + &eval, + omega_in, + unguided_bsdf_pdf, + sampled_rougness, + eta); +# if 0 + if (*unguided_bsdf_pdf > 0.0f) { + surface_shader_validate_bsdf_sample(kg, sc, *omega_in, label, sampled_roughness, eta); + } +# endif + + if (*unguided_bsdf_pdf != 0.0f) { + bsdf_eval_init(bsdf_eval, sc->type, eval * sc->weight); + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + + if (sd->num_closure > 1) { + float sweight = sc->sample_weight; + *unguided_bsdf_pdf = _surface_shader_bsdf_eval_mis( + kg, sd, *omega_in, sc, bsdf_eval, (*unguided_bsdf_pdf) * sweight, sweight, 0); + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + } + *bsdf_pdf = *unguided_bsdf_pdf; + + if (use_surface_guiding) { + guide_pdf = guiding_bsdf_pdf(kg, state, *omega_in); + *bsdf_pdf *= 1.0f - guiding_sampling_prob; + *bsdf_pdf += guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob); + } + } + + kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f); + } + + return label; +} +#endif + /* Sample direction for picked BSDF, and return evaluation and pdf for all * BSDFs combined using MIS. */ ccl_device int surface_shader_bsdf_sample_closure(KernelGlobals kg, @@ -281,6 +557,9 @@ ccl_device int surface_shader_bsdf_sample_closure(KernelGlobals kg, kg, sd, *omega_in, sc, bsdf_eval, *pdf * sweight, sweight, 0); } } + else { + bsdf_eval_init(bsdf_eval, sc->type, zero_spectrum()); + } return label; } diff --git a/intern/cycles/kernel/integrator/volume_shader.h b/intern/cycles/kernel/integrator/volume_shader.h index 31039bfdcf5..d3cfa58db96 100644 --- a/intern/cycles/kernel/integrator/volume_shader.h +++ b/intern/cycles/kernel/integrator/volume_shader.h @@ -22,6 +22,7 @@ CCL_NAMESPACE_BEGIN #ifdef __VOLUME__ /* Merging */ + ccl_device_inline void volume_shader_merge_closures(ccl_private ShaderData *sd) { /* Merge identical closures to save closure space with stacked volumes. */ @@ -88,6 +89,119 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases } } +/* Guiding */ + +# ifdef __PATH_GUIDING__ +ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg, + IntegratorState state, + ccl_private ShaderData *sd, + ccl_private const RNGState *rng_state, + const float3 P, + const float3 D, + ccl_private ShaderVolumePhases *phases, + const VolumeSampleMethod direct_sample_method) +{ + /* Have any phase functions to guide? */ + const int num_phases = phases->num_closure; + if (!kernel_data.integrator.use_volume_guiding || num_phases == 0) { + state->guiding.use_volume_guiding = false; + return; + } + + const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability; + float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING); + + /* If we have more than one ohase function we select one random based on its + * sample weight to caclulate the product distribution for guiding. */ + int phase_id = 0; + float phase_weight = 1.0f; + + if (num_phases > 1) { + /* Pick a phase closure based on sample weights. */ + float sum = 0.0f; + + for (phase_id = 0; phase_id < num_phases; phase_id++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + sum += svc->sample_weight; + } + + float r = rand_phase_guiding * sum; + float partial_sum = 0.0f; + + for (phase_id = 0; phase_id < num_phases; phase_id++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + float next_sum = partial_sum + svc->sample_weight; + + if (r <= next_sum) { + /* Rescale to reuse. */ + rand_phase_guiding = (r - partial_sum) / svc->sample_weight; + phase_weight = svc->sample_weight / sum; + break; + } + + partial_sum = next_sum; + } + + /* Adjust the sample weight of the component used for guiding. */ + phases->closure[phase_id].sample_weight *= volume_guiding_probability; + } + + /* Init guiding for selected phase function. */ + ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; + if (!guiding_phase_init(kg, state, P, D, svc->g, rand_phase_guiding)) { + state->guiding.use_volume_guiding = false; + return; + } + + state->guiding.use_volume_guiding = true; + state->guiding.sample_volume_guiding_rand = rand_phase_guiding; + state->guiding.volume_guiding_sampling_prob = volume_guiding_probability * phase_weight; + + kernel_assert(state->guiding.volume_guiding_sampling_prob > 0.0f && + state->guiding.volume_guiding_sampling_prob <= 1.0f); +} +# endif + +/* Phase Evaluation & Sampling */ + +/* Randomly sample a volume phase function proportional to ShaderClosure.sample_weight. */ +ccl_device_inline ccl_private const ShaderVolumeClosure *volume_shader_phase_pick( + ccl_private const ShaderVolumePhases *phases, ccl_private float2 *rand_phase) +{ + int sampled = 0; + + if (phases->num_closure > 1) { + /* pick a phase closure based on sample weights */ + float sum = 0.0f; + + for (int i = 0; i < phases->num_closure; i++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; + sum += svc->sample_weight; + } + + float r = (*rand_phase).x * sum; + float partial_sum = 0.0f; + + for (int i = 0; i < phases->num_closure; i++) { + ccl_private const ShaderVolumeClosure *svc = &phases->closure[i]; + float next_sum = partial_sum + svc->sample_weight; + + if (r <= next_sum) { + /* Rescale to reuse for volume phase direction sample. */ + sampled = i; + (*rand_phase).x = (r - partial_sum) / svc->sample_weight; + break; + } + + partial_sum = next_sum; + } + } + + /* todo: this isn't quite correct, we don't weight anisotropy properly + * depending on color channels, even if this is perhaps not a common case */ + return &phases->closure[sampled]; +} + ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderData *sd, ccl_private const ShaderVolumePhases *phases, const float3 omega_in, @@ -117,88 +231,139 @@ ccl_device_inline float _volume_shader_phase_eval_mis(ccl_private const ShaderDa ccl_device float volume_shader_phase_eval(KernelGlobals kg, ccl_private const ShaderData *sd, - ccl_private const ShaderVolumePhases *phases, + ccl_private const ShaderVolumeClosure *svc, const float3 omega_in, ccl_private BsdfEval *phase_eval) { - bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); + float phase_pdf = 0.0f; + Spectrum eval = volume_phase_eval(sd, svc, omega_in, &phase_pdf); + + if (phase_pdf != 0.0f) { + bsdf_eval_accum(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + } - return _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f); + return phase_pdf; } -ccl_device int volume_shader_phase_sample(KernelGlobals kg, +ccl_device float volume_shader_phase_eval(KernelGlobals kg, + IntegratorState state, ccl_private const ShaderData *sd, ccl_private const ShaderVolumePhases *phases, - float2 rand_phase, - ccl_private BsdfEval *phase_eval, - ccl_private float3 *omega_in, - ccl_private float *pdf) + const float3 omega_in, + ccl_private BsdfEval *phase_eval) { - int sampled = 0; + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); - if (phases->num_closure > 1) { - /* pick a phase closure based on sample weights */ - float sum = 0.0f; + float pdf = _volume_shader_phase_eval_mis(sd, phases, omega_in, -1, phase_eval, 0.0f, 0.0f); - for (sampled = 0; sampled < phases->num_closure; sampled++) { - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - sum += svc->sample_weight; - } +# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 + if (state->guiding.use_volume_guiding) { + const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob; + const float guide_pdf = guiding_phase_pdf(kg, state, omega_in); + pdf = (guiding_sampling_prob * guide_pdf) + (1.0f - guiding_sampling_prob) * pdf; + } +# endif - float r = rand_phase.x * sum; - float partial_sum = 0.0f; + return pdf; +} - for (sampled = 0; sampled < phases->num_closure; sampled++) { - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - float next_sum = partial_sum + svc->sample_weight; +# ifdef __PATH_GUIDING__ +ccl_device int volume_shader_phase_guided_sample(KernelGlobals kg, + IntegratorState state, + ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + const float2 rand_phase, + ccl_private BsdfEval *phase_eval, + ccl_private float3 *omega_in, + ccl_private float *phase_pdf, + ccl_private float *unguided_phase_pdf, + ccl_private float *sampled_roughness) +{ + const bool use_volume_guiding = state->guiding.use_volume_guiding; + const float guiding_sampling_prob = state->guiding.volume_guiding_sampling_prob; + + /* Decide between sampling guiding distribution and phase. */ + float rand_phase_guiding = state->guiding.sample_volume_guiding_rand; + bool sample_guiding = false; + if (use_volume_guiding && rand_phase_guiding < guiding_sampling_prob) { + sample_guiding = true; + rand_phase_guiding /= guiding_sampling_prob; + } + else { + rand_phase_guiding -= guiding_sampling_prob; + rand_phase_guiding /= (1.0f - guiding_sampling_prob); + } - if (r <= next_sum) { - /* Rescale to reuse for BSDF direction sample. */ - rand_phase.x = (r - partial_sum) / svc->sample_weight; - break; - } + /* Initialize to zero. */ + int label = LABEL_NONE; + Spectrum eval = zero_spectrum(); - partial_sum = next_sum; - } + *unguided_phase_pdf = 0.0f; + float guide_pdf = 0.0f; + *sampled_roughness = 1.0f - fabsf(svc->g); + + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); - if (sampled == phases->num_closure) { - *pdf = 0.0f; - return LABEL_NONE; + if (sample_guiding) { + /* Sample guiding distribution. */ + guide_pdf = guiding_phase_sample(kg, state, rand_phase, omega_in); + *phase_pdf = 0.0f; + + if (guide_pdf != 0.0f) { + *unguided_phase_pdf = volume_shader_phase_eval(kg, sd, svc, *omega_in, phase_eval); + *phase_pdf = (guiding_sampling_prob * guide_pdf) + + ((1.0f - guiding_sampling_prob) * (*unguided_phase_pdf)); + label = LABEL_VOLUME_SCATTER; } } + else { + /* Sample phase. */ + *phase_pdf = 0.0f; + label = volume_phase_sample( + sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, unguided_phase_pdf); + + if (*unguided_phase_pdf != 0.0f) { + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + + *phase_pdf = *unguided_phase_pdf; + if (use_volume_guiding) { + guide_pdf = guiding_phase_pdf(kg, state, *omega_in); + *phase_pdf *= 1.0f - guiding_sampling_prob; + *phase_pdf += guiding_sampling_prob * guide_pdf; + } - /* todo: this isn't quite correct, we don't weight anisotropy properly - * depending on color channels, even if this is perhaps not a common case */ - ccl_private const ShaderVolumeClosure *svc = &phases->closure[sampled]; - int label; - Spectrum eval = zero_spectrum(); - - *pdf = 0.0f; - label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); + kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f); + } + else { + bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, zero_spectrum()); + } - if (*pdf != 0.0f) { - bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f); } return label; } +# endif -ccl_device int volume_shader_phase_sample_closure(KernelGlobals kg, - ccl_private const ShaderData *sd, - ccl_private const ShaderVolumeClosure *sc, - const float2 rand_phase, - ccl_private BsdfEval *phase_eval, - ccl_private float3 *omega_in, - ccl_private float *pdf) +ccl_device int volume_shader_phase_sample(KernelGlobals kg, + ccl_private const ShaderData *sd, + ccl_private const ShaderVolumePhases *phases, + ccl_private const ShaderVolumeClosure *svc, + float2 rand_phase, + ccl_private BsdfEval *phase_eval, + ccl_private float3 *omega_in, + ccl_private float *pdf, + ccl_private float *sampled_roughness) { - int label; + *sampled_roughness = 1.0f - fabsf(svc->g); Spectrum eval = zero_spectrum(); *pdf = 0.0f; - label = volume_phase_sample(sd, sc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); + int label = volume_phase_sample(sd, svc, rand_phase.x, rand_phase.y, &eval, omega_in, pdf); - if (*pdf != 0.0f) + if (*pdf != 0.0f) { bsdf_eval_init(phase_eval, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, eval); + } return label; } |