Path Guiding: Adding guiding on glossy surfaces via RIS #107782

Merged
Sebastian Herholz merged 1 commits from sherholz/blender:cycles_path_guiding_RIS into main 2023-05-22 16:47:10 +02:00
11 changed files with 550 additions and 33 deletions

View File

@ -209,6 +209,21 @@ enum_guiding_distribution = (
('VMM', "VMM", "Use von Mises-Fisher models as directional distribution", 2),
)
enum_guiding_directional_sampling_types = (
('MIS',
"Diffuse Product MIS",
"Guided diffuse BSDF component based on the incoming light distribution and the cosine product (closed form product)",
0),
('RIS',
"Re-sampled Importance Sampling",
"Perform RIS sampling to guided based on the product of the incoming light distribution and the BSDF",
1),
('ROUGHNESS',
"Roughness-based",
"Adjust the guiding probability based on the roughness of the material components",
2),
)
def enum_openimagedenoise_denoiser(self, context):
import _cycles
@ -568,6 +583,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default='PARALLAX_AWARE_VMM',
)
guiding_directional_sampling_type: EnumProperty(
name="Directional Sampling Type",
description="Type of the directional sampling used for guiding",
items=enum_guiding_directional_sampling_types,
default='RIS',
)
use_surface_guiding: BoolProperty(
name="Surface Guiding",
description="Use guiding when sampling directions on a surface",
@ -617,6 +639,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default=True,
)
guiding_roughness_threshold: FloatProperty(
name="Guiding Roughness Threshold",
description="The minimal roughness value of a material to apply guiding",
min=0.0, max=1.0,
default=0.05,
)
max_bounces: IntProperty(
name="Max Bounces",
description="Total maximum number of bounces",

View File

@ -337,6 +337,8 @@ class CYCLES_RENDER_PT_sampling_path_guiding_debug(CyclesDebugButtonsPanel, Pane
layout.active = cscene.use_guiding
layout.prop(cscene, "guiding_distribution_type", text="Distribution Type")
layout.prop(cscene, "guiding_roughness_threshold")
layout.prop(cscene, "guiding_directional_sampling_type", text="Directional Sampling Type")
col = layout.column(align=True)
col.prop(cscene, "surface_guiding_probability")

View File

@ -440,6 +440,13 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
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);
GuidingDirectionalSamplingType guiding_directional_sampling_type =
(GuidingDirectionalSamplingType)get_enum(cscene,
"guiding_directional_sampling_type",
GUIDING_DIRECTIONAL_SAMPLING_NUM_TYPES,
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS);
integrator->set_guiding_directional_sampling_type(guiding_directional_sampling_type);
integrator->set_guiding_roughness_threshold(get_float(cscene, "guiding_roughness_threshold"));
}
DenoiseParams denoise_params = get_denoise_params(b_scene, b_view_layer, background);

View File

@ -15,6 +15,8 @@ struct GuidingParams {
bool use_volume_guiding = false;
GuidingDistributionType type = GUIDING_TYPE_PARALLAX_AWARE_VMM;
GuidingDirectionalSamplingType sampling_type = GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS;
float roughness_threshold = 0.05f;
int training_samples = 128;
bool deterministic = false;
@ -24,7 +26,9 @@ struct GuidingParams {
{
return !((use == other.use) && (use_surface_guiding == other.use_surface_guiding) &&
(use_volume_guiding == other.use_volume_guiding) && (type == other.type) &&
(sampling_type == other.sampling_type) &&
(training_samples == other.training_samples) &&
(roughness_threshold == other.roughness_threshold) &&
(deterministic == other.deterministic));
}
};

View File

@ -207,6 +207,8 @@ KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type)
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, guiding_directional_sampling_type)
KERNEL_STRUCT_MEMBER(integrator, float, guiding_roughness_threshold)
KERNEL_STRUCT_MEMBER(integrator, int, use_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, train_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, use_surface_guiding)
@ -216,6 +218,8 @@ KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
/* Padding. */
KERNEL_STRUCT_MEMBER(integrator, int, pad1)
KERNEL_STRUCT_MEMBER(integrator, int, pad2)
KERNEL_STRUCT_MEMBER(integrator, int, pad3)
KERNEL_STRUCT_END(KernelIntegrator)
/* SVM. For shader specialization. */

View File

@ -7,10 +7,66 @@
#include "kernel/closure/bsdf.h"
#include "kernel/film/write.h"
#if OPENPGL_VERSION_MINOR >= 5
# define RIS_INCOMING_RADIANCE
#endif
CCL_NAMESPACE_BEGIN
/* Utilities. */
struct GuidingRISSample {
float3 rand;
float2 sampled_roughness;
float eta{1.0f};
int label;
float3 wo;
float bsdf_pdf{0.0f};
float guide_pdf{0.0f};
float ris_target{0.0f};
float ris_pdf{0.0f};
float ris_weight{0.0f};
#ifdef RIS_INCOMING_RADIANCE
float incoming_radiance_pdf{0.0f};
#else
float cosine{0.0f};
#endif
BsdfEval bsdf_eval;
float avg_bsdf_eval{0.0f};
Spectrum eval{zero_spectrum()};
};
ccl_device_forceinline bool calculate_ris_target(ccl_private GuidingRISSample *ris_sample,
brecht marked this conversation as resolved Outdated

We use snake style for variables, so ris_sample .

We use snake style for variables, so `ris_sample `.

Done

Done
ccl_private const float guiding_sampling_prob)
{
#if defined(__PATH_GUIDING__)
const float pi_factor = 2.0f;
if (ris_sample->avg_bsdf_eval > 0.0f && ris_sample->bsdf_pdf > 1e-10f &&
ris_sample->guide_pdf > 0.0f)
{
# ifdef RIS_INCOMING_RADIANCE
ris_sample->ris_target = (ris_sample->avg_bsdf_eval *
((((1.0f - guiding_sampling_prob) * (1.0f / (pi_factor * M_PI_F))) +
(guiding_sampling_prob * ris_sample->incoming_radiance_pdf))));
# else
ris_sample->ris_target = (ris_sample->avg_bsdf_eval / ris_sample->cosine *
((((1.0f - guiding_sampling_prob) * (1.0f / (pi_factor * M_PI_F))) +
(guiding_sampling_prob * ris_sample->guide_pdf))));
# endif
ris_sample->ris_pdf = (0.5f * (ris_sample->bsdf_pdf + ris_sample->guide_pdf));
ris_sample->ris_weight = ris_sample->ris_target / ris_sample->ris_pdf;
return true;
}
ris_sample->ris_target = 0.0f;
ris_sample->ris_pdf = 0.0f;
return false;
#else
return false;
#endif
}
#if defined(__PATH_GUIDING__)
static pgl_vec3f guiding_vec3f(const float3 v)
{
@ -241,7 +297,7 @@ ccl_device_forceinline void guiding_record_volume_bounce(KernelGlobals kg,
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::SetEta(state->guiding.path_segment, 1.0f);
openpgl::cpp::SetRoughness(state->guiding.path_segment, roughness);
#endif
}
@ -259,11 +315,11 @@ ccl_device_forceinline void guiding_record_volume_transmission(KernelGlobals kg,
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]) ||
if ((transmittance_weight[0] < 0.0f || !std::isfinite(transmittance_weight[0]) ||
std::isnan(transmittance_weight[0])) ||
(transmittance_weight[1] < 0.f || !std::isfinite(transmittance_weight[1]) ||
(transmittance_weight[1] < 0.0f || !std::isfinite(transmittance_weight[1]) ||
std::isnan(transmittance_weight[1])) ||
(transmittance_weight[2] < 0.f || !std::isfinite(transmittance_weight[2]) ||
(transmittance_weight[2] < 0.0f || !std::isfinite(transmittance_weight[2]) ||
std::isnan(transmittance_weight[2])))
{
}
@ -438,7 +494,7 @@ ccl_device_forceinline void guiding_write_debug_passes(KernelGlobals kg,
sum_sample_weight += sc->sample_weight;
}
avg_roughness = avg_roughness > 0.f ? avg_roughness / sum_sample_weight : 0.f;
avg_roughness = avg_roughness > 0.0f ? avg_roughness / sum_sample_weight : 0.0f;
film_write_pass_float(buffer + kernel_data.film.pass_guiding_avg_roughness, avg_roughness);
}
@ -498,6 +554,17 @@ ccl_device_forceinline float guiding_bsdf_pdf(KernelGlobals kg,
#endif
}
ccl_device_forceinline float guiding_surface_incoming_radiance_pdf(KernelGlobals kg,
IntegratorState state,
const float3 wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4 && OPENPGL_VERSION_MINOR >= 5
return kg->opgl_surface_sampling_distribution->IncomingRadiancePDF(guiding_vec3f(wo));
#else
return 0.0f;
#endif
}
/* Guided Volume Phases */
ccl_device_forceinline bool guiding_phase_init(KernelGlobals kg,

View File

@ -372,6 +372,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
float2 bsdf_sampled_roughness = make_float2(1.0f, 1.0f);
float bsdf_eta = 1.0f;
float mis_pdf = 1.0f;
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (kernel_data.integrator.use_surface_guiding) {
@ -383,9 +384,11 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
&bsdf_eval,
&bsdf_wo,
&bsdf_pdf,
&mis_pdf,
&unguided_bsdf_pdf,
&bsdf_sampled_roughness,
&bsdf_eta);
&bsdf_eta,
rng_state);
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
return LABEL_NONE;
@ -410,7 +413,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
return LABEL_NONE;
}
mis_pdf = bsdf_pdf;
unguided_bsdf_pdf = bsdf_pdf;
}
@ -445,7 +448,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
/* Update path state */
if (!(label & LABEL_TRANSPARENT)) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = mis_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));

View File

@ -24,6 +24,28 @@ CCL_NAMESPACE_BEGIN
/* Guiding */
brecht marked this conversation as resolved Outdated

Is it possible to remove the permutation of the RIS_COSINE and RIS_INCOMMING_RADIANCE?

Is it possible to remove the permutation of the `RIS_COSINE` and `RIS_INCOMMING_RADIANCE`?

Done,
The other define # define RIS_INCOMING_RADIANCE will be gone after we bumped the Open PGL to 0.5.0

Done, The other define `# define RIS_INCOMING_RADIANCE` will be gone after we bumped the Open PGL to 0.5.0

I think we can make the 0.5 version required.

Other than that, I am not a fan of such implicit assumption that the OPENPGL_VERSION_MAJOR is 0. We really need to have an utility macro like I've shown in the comment in the #106861.

I think we can make the 0.5 version required. Other than that, I am not a fan of such implicit assumption that the `OPENPGL_VERSION_MAJOR` is 0. We really need to have an utility macro like I've shown in the comment in the #106861.

I already talked with Ray. As soon as every dependency package updated to Open PGL 0.5 I would like to bump the minimal required version to 0.5 and get rid of all these defines.

I already talked with Ray. As soon as every dependency package updated to Open PGL 0.5 I would like to bump the minimal required version to 0.5 and get rid of all these defines.

OpenPGL was updated to 0.5 for all platforms, so this can be simplified.

OpenPGL was updated to 0.5 for all platforms, so this can be simplified.
#ifdef __PATH_GUIDING__
ccl_device float surface_shader_average_sample_weight_squared_roughness(
ccl_private const ShaderData *sd)
{
float avg_squared_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_squared_roughness += sc->sample_weight * bsdf_get_specular_roughness_squared(sc);
sum_sample_weight += sc->sample_weight;
}
avg_squared_roughness = avg_squared_roughness > 0.0f ?
avg_squared_roughness / sum_sample_weight :
0.0f;
return avg_squared_roughness;
}
ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
@ -36,6 +58,9 @@ ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg,
}
const float surface_guiding_probability = kernel_data.integrator.surface_guiding_probability;
const int guiding_directional_sampling_type =
kernel_data.integrator.guiding_directional_sampling_type;
const float guiding_roughness_threshold = kernel_data.integrator.guiding_roughness_threshold;
float rand_bsdf_guiding = path_state_rng_1D(kg, rng_state, PRNG_SURFACE_BSDF_GUIDING);
/* Compute proportion of diffuse BSDF and BSSRDFs. */
@ -43,6 +68,8 @@ ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg,
float bssrdf_sampling_fraction = 0.0f;
float bsdf_bssrdf_sampling_sum = 0.0f;
bool fully_opaque = true;
for (int i = 0; i < sd->num_closure; i++) {
ShaderClosure *sc = &sd->closure[i];
if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) {
@ -56,6 +83,10 @@ ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg,
if (CLOSURE_IS_BSSRDF(sc->type)) {
bssrdf_sampling_fraction += sweight;
}
if (CLOSURE_IS_BSDF_TRANSPARENT(sc->type) || CLOSURE_IS_BSDF_TRANSMISSION(sc->type)) {
fully_opaque = false;
}
}
}
@ -64,17 +95,36 @@ ccl_device_inline void surface_shader_prepare_guiding(KernelGlobals kg,
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)))
/* Init guiding */
/* The the roughness because the function returns alpha.x * alpha.y. In addition alpha is squared
brecht marked this conversation as resolved Outdated

Units seems a bit odd: comparison of toughness with roughness^2. Is this expected?

Units seems a bit odd: comparison of toughness with roughness^2. Is this expected?

This is due to the fact that Blender/Cycles uses roughness^2 internally and squared roughness returns alpha.x * alpha.y so it is actually (roughness^2)^2 compared to the user input.
I refactored everything to make this more clear and convert the guiding_roughness_threshold to be guiding_roughness_threshold^2.

This is due to the fact that Blender/Cycles uses `roughness^2` internally and squared roughness returns `alpha.x * alpha.y` so it is actually `(roughness^2)^2` compared to the user input. I refactored everything to make this more clear and convert the `guiding_roughness_threshold` to be `guiding_roughness_threshold^2`.
* again */
float avg_roughness = surface_shader_average_sample_weight_squared_roughness(sd);
avg_roughness = safe_sqrtf(avg_roughness);
if (!fully_opaque || avg_roughness < guiding_roughness_threshold ||
((guiding_directional_sampling_type == GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS) &&
(diffuse_sampling_fraction <= 0.0f)) ||
!guiding_bsdf_init(kg, state, sd->P, sd->N, rand_bsdf_guiding))
{
state->guiding.use_surface_guiding = false;
state->guiding.surface_guiding_sampling_prob = 0.0f;
return;
}
state->guiding.use_surface_guiding = true;
state->guiding.surface_guiding_sampling_prob = surface_guiding_probability *
diffuse_sampling_fraction;
if (kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS)
{
state->guiding.surface_guiding_sampling_prob = surface_guiding_probability *
diffuse_sampling_fraction;
}
else if (kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS)
{
state->guiding.surface_guiding_sampling_prob = surface_guiding_probability;
}
else { // GUIDING_DIRECTIONAL_SAMPLING_TYPE_ROUGHNESS
state->guiding.surface_guiding_sampling_prob = surface_guiding_probability * avg_roughness;
}
state->guiding.bssrdf_sampling_prob = bssrdf_sampling_fraction;
state->guiding.sample_surface_guiding_rand = rand_bsdf_guiding;
@ -325,12 +375,20 @@ ccl_device_inline
kg, sd, wo, NULL, bsdf_eval, 0.0f, 0.0f, light_shader_flags);
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (state->guiding.use_surface_guiding) {
if (pdf > 0.0f && 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, wo);
pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) +
(1.0f - guiding_sampling_prob) * pdf;
if (kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS)
{
pdf = (0.5f * guide_pdf * (1.0f - bssrdf_sampling_prob)) + 0.5f * pdf;
}
else {
pdf = (guiding_sampling_prob * guide_pdf * (1.0f - bssrdf_sampling_prob)) +
(1.0f - guiding_sampling_prob) * pdf;
}
}
#endif
@ -406,17 +464,17 @@ surface_shader_bssrdf_sample_weight(ccl_private const ShaderData *ccl_restrict s
/* 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 float3 rand_bsdf,
ccl_private BsdfEval *bsdf_eval,
ccl_private float3 *wo,
ccl_private float *bsdf_pdf,
ccl_private float *unguided_bsdf_pdf,
ccl_private float2 *sampled_rougness,
ccl_private float *eta)
ccl_device int surface_shader_bsdf_guided_sample_closure_mis(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
ccl_private const ShaderClosure *sc,
const float3 rand_bsdf,
ccl_private BsdfEval *bsdf_eval,
ccl_private float3 *wo,
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));
@ -478,6 +536,10 @@ ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg,
label = bsdf_label(kg, &sd->closure[idx], *wo);
}
else {
*bsdf_pdf = 0.0f;
*unguided_bsdf_pdf = 0.0f;
}
}
kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f);
@ -499,6 +561,9 @@ ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg,
sampled_rougness,
eta);
# if 0
// Code path to validate the estimation of the label, sampled roughness and eta
// This should be activated from time to time when the BSDFs change to check if everything
// is still working correctly.
if (*unguided_bsdf_pdf > 0.0f) {
surface_shader_validate_bsdf_sample(kg, sc, *wo, label, sampled_roughness, eta);
}
@ -529,6 +594,309 @@ ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg,
return label;
}

Is there a link to a paper we can add here to help understanding all the math which is going on in the function?

Is there a link to a paper we can add here to help understanding all the math which is going on in the function?

This is an extension/variant of the standard RIS where I added some stuff.
A detailed description of the method with all the insides will be part of the Siggraph2023 Course on path guiding, I can add a link as soon as the course notes are online.

This is an extension/variant of the standard RIS where I added some stuff. A detailed description of the method with all the insides will be part of the Siggraph2023 Course on path guiding, I can add a link as soon as the course notes are online.
ccl_device int surface_shader_bsdf_guided_sample_closure_ris(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
ccl_private const ShaderClosure *sc,
const float3 rand_bsdf,
ccl_private const RNGState *rng_state,
ccl_private BsdfEval *bsdf_eval,
ccl_private float3 *wo,
ccl_private float *bsdf_pdf,
ccl_private float *mis_pdf,
ccl_private float *unguided_bsdf_pdf,
ccl_private float2 *sampled_roughness,
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. */
float rand_bsdf_guiding = state->guiding.sample_surface_guiding_rand;
/* 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 (use_surface_guiding && guiding_sampling_prob > 0.0f) {
/* Performing guided sampling using RIS */
// selected RIS candidate
int ris_idx = 0;
// meta data for the two RIS candidates
GuidingRISSample ris_samples[2];
ris_samples[0].rand = rand_bsdf;
ris_samples[1].rand = path_state_rng_3D(kg, rng_state, PRNG_SURFACE_RIS_GUIDING_0);
// ----------------------------------------------------
// generate the first RIS candidate using a BSDF sample
brecht marked this conversation as resolved Outdated

Here and everywhere else in the patch: do not omit the 0 suffix (0.0f, not 0.f).

This is per the style guide: https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Value_Literals

Here and everywhere else in the patch: do not omit the 0 suffix (0.0f, not 0.f). This is per the style guide: https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Value_Literals

Done

Done
// ----------------------------------------------------
ris_samples[0].label = bsdf_sample(kg,
sd,
sc,
INTEGRATOR_STATE(state, path, flag),
ris_samples[0].rand,
&ris_samples[0].eval,
&ris_samples[0].wo,
&ris_samples[0].bsdf_pdf,
&ris_samples[0].sampled_roughness,
&ris_samples[0].eta);
bsdf_eval_init(&ris_samples[0].bsdf_eval, sc->type, ris_samples[0].eval * sc->weight);
if (ris_samples[0].bsdf_pdf > 0.0f) {
if (sd->num_closure > 1) {
float sweight = sc->sample_weight;
ris_samples[0].bsdf_pdf = _surface_shader_bsdf_eval_mis(kg,
sd,
ris_samples[0].wo,
sc,
&ris_samples[0].bsdf_eval,
(ris_samples[0].bsdf_pdf) *
sweight,
sweight,
0);
kernel_assert(reduce_min(bsdf_eval_sum(&ris_samples[0].bsdf_eval)) >= 0.0f);
}
ris_samples[0].avg_bsdf_eval = average(ris_samples[0].bsdf_eval.sum);
ris_samples[0].guide_pdf = guiding_bsdf_pdf(kg, state, ris_samples[0].wo);
ris_samples[0].guide_pdf *= (1.0f - bssrdf_sampling_prob);
# ifdef RIS_INCOMING_RADIANCE
ris_samples[0].incoming_radiance_pdf = guiding_surface_incoming_radiance_pdf(
kg, state, ris_samples[0].wo);
# else
ris_samples[0].cosine = max(0.01f, fabsf(dot(sd->N, ris_samples[0].wo)));
# endif
ris_samples[0].bsdf_pdf = max(0.0f, ris_samples[0].bsdf_pdf);
}
// ------------------------------------------------------------------------------
// generate the second RIS candidate using a sample from the guiding distribution
// ------------------------------------------------------------------------------
float unguided_bsdf_pdfs[MAX_CLOSURE];
bsdf_eval_init(&ris_samples[1].bsdf_eval, CLOSURE_NONE_ID, eval);
ris_samples[1].guide_pdf = guiding_bsdf_sample(
kg, state, float3_to_float2(ris_samples[1].rand), &ris_samples[1].wo);
ris_samples[1].guide_pdf *= (1.0f - bssrdf_sampling_prob);
# ifdef RIS_INCOMING_RADIANCE
ris_samples[1].incoming_radiance_pdf = guiding_surface_incoming_radiance_pdf(
kg, state, ris_samples[1].wo);
# else
ris_samples[1].cosine = max(0.01f, fabsf(dot(sd->N, ris_samples[1].wo)));
sherholz marked this conversation as resolved Outdated

Use average()

Use `average()`

Done

Done
# endif
ris_samples[1].bsdf_pdf = surface_shader_bsdf_eval_pdfs(
kg, sd, ris_samples[1].wo, &ris_samples[1].bsdf_eval, unguided_bsdf_pdfs, 0);
ris_samples[1].label = ris_samples[0].label;
ris_samples[1].avg_bsdf_eval = average(ris_samples[1].bsdf_eval.sum);
ris_samples[1].bsdf_pdf = max(0.0f, ris_samples[1].bsdf_pdf);
// ------------------------------------------------------------------------------
// calculate the RIS target functions for each RIS candidate
// ------------------------------------------------------------------------------
int num_ris_candidates = 0;
float sum_ris_weights = 0.0f;
if (calculate_ris_target(&ris_samples[0], guiding_sampling_prob)) {
sum_ris_weights += ris_samples[0].ris_weight;
num_ris_candidates++;
}
kernel_assert(ris_samples[0].ris_weight >= 0.0f);
kernel_assert(sum_ris_weights >= 0.0f);
if (calculate_ris_target(&ris_samples[1], guiding_sampling_prob)) {
sum_ris_weights += ris_samples[1].ris_weight;
brecht marked this conversation as resolved Outdated

Should this be instead kernel_assert(risSamples[1].ris_weight >= 0.0f) ?
Otherwise if the first RIS sample weight is 0.5, and the second RIS sample is -0.1 we will not catch the issue with this assert.

Should this be instead `kernel_assert(risSamples[1].ris_weight >= 0.0f)` ? Otherwise if the first RIS sample weight is 0.5, and the second RIS sample is -0.1 we will not catch the issue with this assert.

I agree, I changed it to test in both cases for the ris_weight as well

I agree, I changed it to test in both cases for the `ris_weight` as well
num_ris_candidates++;
}
kernel_assert(ris_samples[1].ris_weight >= 0.0f);
kernel_assert(sum_ris_weights >= 0.0f);
// ------------------------------------------------------------------------------
// Sample/Select a sample from the RIS candidates proportional to the target
// ------------------------------------------------------------------------------
if (num_ris_candidates == 0 || !(sum_ris_weights > 1e-10f)) {
*bsdf_pdf = 0.0f;
*mis_pdf = 0.0f;
return label;
}
float rand_ris_select = rand_bsdf_guiding * sum_ris_weights;
float sum_ris = 0.0f;
for (int i = 0; i < 2; i++) {
sum_ris += ris_samples[i].ris_weight;
if (rand_ris_select <= sum_ris) {
ris_idx = i;
break;
}
}
kernel_assert(sum_ris >= 0.0f);
kernel_assert(ris_idx < 2);
// ------------------------------------------------------------------------------
// Fill in the sample data for the selected RIS candidate
// ------------------------------------------------------------------------------
guide_pdf = ris_samples[ris_idx].ris_target * (2.0f / sum_ris_weights);
*unguided_bsdf_pdf = ris_samples[ris_idx].bsdf_pdf;
*mis_pdf = 0.5f * (ris_samples[ris_idx].bsdf_pdf + ris_samples[ris_idx].guide_pdf);
*bsdf_pdf = guide_pdf;
*wo = ris_samples[ris_idx].wo;
label = ris_samples[ris_idx].label;
*sampled_roughness = ris_samples[ris_idx].sampled_roughness;
*eta = ris_samples[ris_idx].eta;
*bsdf_eval = ris_samples[ris_idx].bsdf_eval;
kernel_assert(isfinite_safe(guide_pdf));
kernel_assert(isfinite_safe(*bsdf_pdf));
if (!(*bsdf_pdf > 1e-10f)) {
*bsdf_pdf = 0.0f;
*mis_pdf = 0.0f;
return label;
}
kernel_assert(*bsdf_pdf > 0.0f);
kernel_assert(*bsdf_pdf >= 1e-20f);
kernel_assert(guide_pdf >= 0.0f);
/// select label sampled_roughness and eta
if (ris_idx == 1 && ris_samples[1].bsdf_pdf > 0.0f) {
float rnd = path_state_rng_1D(kg, rng_state, PRNG_SURFACE_RIS_GUIDING_1);
float sum_pdfs = 0.0f;
int idx = -1;
for (int i = 0; i < sd->num_closure; i++) {
sum_pdfs += unguided_bsdf_pdfs[i];
if (rnd <= 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 = (rnd > sum_pdfs) ? sd->num_closure - 1 : idx;
label = bsdf_label(kg, &sd->closure[idx], *wo);
bsdf_roughness_eta(kg, &sd->closure[idx], sampled_roughness, eta);
}
kernel_assert(isfinite_safe(*bsdf_pdf));
kernel_assert(*bsdf_pdf >= 0.0f);
kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f);
}
else {
/* Sample BSDF. */
*bsdf_pdf = 0.0f;
label = bsdf_sample(kg,
sd,
sc,
INTEGRATOR_STATE(state, path, flag),
rand_bsdf,
&eval,
wo,

Is it intended for the merge?

Is it intended for the merge?

It is mainly there to be able to validate the label, roughness and eta estimation.
this will be useful when the BSDF models change (e.g., after Principle_v2).

Do you have a better idea or suggestion?

It is mainly there to be able to validate the label, roughness and eta estimation. this will be useful when the BSDF models change (e.g., after Principle_v2). Do you have a better idea or suggestion?

Not really, was just curious if it is something left over from earlier development, or still intended to be useful for debugging.

One idea could be to use #ifdef WITH_CYCLES_DEBUG, but not really sure.

Not really, was just curious if it is something left over from earlier development, or still intended to be useful for debugging. One idea could be to use `#ifdef WITH_CYCLES_DEBUG`, but not really sure.

I think it's fine as is, but could use a comment.

I think it's fine as is, but could use a comment.

done

done

done

done
unguided_bsdf_pdf,
sampled_roughness,
eta);
# if 0
// Code path to validate the estimation of the label, sampled roughness and eta
// This should be activated from time to time when the BSDFs change to check if everything
// is still working correctly.
if (*unguided_bsdf_pdf > 0.0f) {
surface_shader_validate_bsdf_sample(kg, sc, *wo, 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, *wo, 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;
*mis_pdf = *bsdf_pdf;
}
kernel_assert(reduce_min(bsdf_eval_sum(bsdf_eval)) >= 0.0f);
}
return label;
}
ccl_device int surface_shader_bsdf_guided_sample_closure(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
ccl_private const ShaderClosure *sc,
const float3 rand_bsdf,
ccl_private BsdfEval *bsdf_eval,
ccl_private float3 *wo,
ccl_private float *bsdf_pdf,
ccl_private float *mis_pdf,
ccl_private float *unguided_bsdf_pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta,
ccl_private const RNGState *rng_state)
{
int label;
if (kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS ||
kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_ROUGHNESS)
{
label = surface_shader_bsdf_guided_sample_closure_mis(kg,
state,
sd,
sc,
rand_bsdf,
bsdf_eval,
wo,
bsdf_pdf,
unguided_bsdf_pdf,
sampled_roughness,
eta);
*mis_pdf = (*unguided_bsdf_pdf > 0.0f) ? *bsdf_pdf : 0.0f;
}
else if (kernel_data.integrator.guiding_directional_sampling_type ==
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS)
{
label = surface_shader_bsdf_guided_sample_closure_ris(kg,
state,
sd,
sc,
rand_bsdf,
rng_state,
bsdf_eval,
wo,
bsdf_pdf,
mis_pdf,
unguided_bsdf_pdf,
sampled_roughness,
eta);
}
if (!(*unguided_bsdf_pdf > 0.0f)) {
*bsdf_pdf = 0.0f;
*mis_pdf = 0.0f;
}
return label;
}
#endif
/* Sample direction for picked BSDF, and return evaluation and pdf for all

View File

@ -163,6 +163,11 @@ enum PathTraceDimension {
PRNG_SURFACE_AO = 4,
PRNG_SURFACE_BEVEL = 5,
PRNG_SURFACE_BSDF_GUIDING = 6,
/* Guiding RIS */
PRNG_SURFACE_RIS_GUIDING_0 = 10,
PRNG_SURFACE_RIS_GUIDING_1 = 11,
/* Volume */
PRNG_VOLUME_PHASE = 3,
PRNG_VOLUME_PHASE_CHANNEL = 4,
@ -506,6 +511,16 @@ typedef enum GuidingDistributionType {
GUIDING_NUM_TYPES,
} GuidingDistributionType;
/* Guiding Directional Sampling Type */
typedef enum GuidingDirectionalSamplingType {
GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS = 0,
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS = 1,
GUIDING_DIRECTIONAL_SAMPLING_TYPE_ROUGHNESS = 2,
GUIDING_DIRECTIONAL_SAMPLING_NUM_TYPES,
} GuidingDirectionalSamplingType;
/* Camera Type */
enum CameraType { CAMERA_PERSPECTIVE, CAMERA_ORTHOGRAPHIC, CAMERA_PANORAMA };

View File

@ -60,10 +60,17 @@ 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);
static NodeEnum guiding_distribution_enum;
guiding_distribution_enum.insert("PARALLAX_AWARE_VMM", GUIDING_TYPE_PARALLAX_AWARE_VMM);
guiding_distribution_enum.insert("DIRECTIONAL_QUAD_TREE", GUIDING_TYPE_DIRECTIONAL_QUAD_TREE);
guiding_distribution_enum.insert("VMM", GUIDING_TYPE_VMM);
static NodeEnum guiding_directional_sampling_type_enum;
guiding_directional_sampling_type_enum.insert("MIS",
GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS);
guiding_directional_sampling_type_enum.insert("RIS", GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS);
guiding_directional_sampling_type_enum.insert("ROUGHNESS",
GUIDING_DIRECTIONAL_SAMPLING_TYPE_ROUGHNESS);
SOCKET_BOOLEAN(use_guiding, "Guiding", false);
SOCKET_BOOLEAN(deterministic_guiding, "Deterministic Guiding", true);
@ -76,8 +83,13 @@ NODE_DEFINE(Integrator)
SOCKET_BOOLEAN(use_guiding_mis_weights, "Use MIS Weights", true);
SOCKET_ENUM(guiding_distribution_type,
"Guiding Distribution Type",
guiding_ditribution_enum,
guiding_distribution_enum,
GUIDING_TYPE_PARALLAX_AWARE_VMM);
SOCKET_ENUM(guiding_directional_sampling_type,
"Guiding Directional Sampling Type",
guiding_directional_sampling_type_enum,
GUIDING_DIRECTIONAL_SAMPLING_TYPE_RIS);
SOCKET_FLOAT(guiding_roughness_threshold, "Guiding Roughness Threshold", 0.05f);
SOCKET_BOOLEAN(caustics_reflective, "Reflective Caustics", true);
SOCKET_BOOLEAN(caustics_refractive, "Refractive Caustics", true);
@ -239,6 +251,8 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
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->guiding_directional_sampling_type = guiding_params.sampling_type;
kintegrator->guiding_roughness_threshold = guiding_params.roughness_threshold;
kintegrator->seed = seed;
@ -409,7 +423,9 @@ GuidingParams Integrator::get_guiding_params(const Device *device) const
guiding_params.type = guiding_distribution_type;
guiding_params.training_samples = guiding_training_samples;
guiding_params.deterministic = deterministic_guiding;
guiding_params.sampling_type = guiding_directional_sampling_type;
// In Blender/Cycles the user set roughness is squared to behave more linear.
guiding_params.roughness_threshold = guiding_roughness_threshold * guiding_roughness_threshold;
return guiding_params;
}
CCL_NAMESPACE_END

View File

@ -54,6 +54,8 @@ class Integrator : public Node {
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(GuidingDirectionalSamplingType, guiding_directional_sampling_type);
NODE_SOCKET_API(float, guiding_roughness_threshold);
NODE_SOCKET_API(bool, caustics_reflective)
NODE_SOCKET_API(bool, caustics_refractive)