diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 442f06c10f1..24960bcb09f 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -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", diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 05f84346d6d..998195c972f 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -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") diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 40979dfda0b..c7432433826 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -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); diff --git a/intern/cycles/integrator/guiding.h b/intern/cycles/integrator/guiding.h index b7d7e2fe51e..c8be1fcb89b 100644 --- a/intern/cycles/integrator/guiding.h +++ b/intern/cycles/integrator/guiding.h @@ -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)); } }; diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h index 8851a2b62c7..2661d362b05 100644 --- a/intern/cycles/kernel/data_template.h +++ b/intern/cycles/kernel/data_template.h @@ -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. */ diff --git a/intern/cycles/kernel/integrator/guiding.h b/intern/cycles/kernel/integrator/guiding.h index d42848ce3c0..c91f8af67bd 100644 --- a/intern/cycles/kernel/integrator/guiding.h +++ b/intern/cycles/kernel/integrator/guiding.h @@ -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, + 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, diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index 4db52ff4093..86000d4ac6c 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -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)); diff --git a/intern/cycles/kernel/integrator/surface_shader.h b/intern/cycles/kernel/integrator/surface_shader.h index a0fa9255628..e0ec44ba5cf 100644 --- a/intern/cycles/kernel/integrator/surface_shader.h +++ b/intern/cycles/kernel/integrator/surface_shader.h @@ -24,6 +24,28 @@ CCL_NAMESPACE_BEGIN /* Guiding */ #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 + * 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; } + +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 + // ---------------------------------------------------- + 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))); +# 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; + 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, + 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 diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 811eaaf2499..26abe14b09b 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -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 }; diff --git a/intern/cycles/scene/integrator.cpp b/intern/cycles/scene/integrator.cpp index 502a6884272..e94ef61ea20 100644 --- a/intern/cycles/scene/integrator.cpp +++ b/intern/cycles/scene/integrator.cpp @@ -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 diff --git a/intern/cycles/scene/integrator.h b/intern/cycles/scene/integrator.h index 92ac3a254b7..35306272a8d 100644 --- a/intern/cycles/scene/integrator.h +++ b/intern/cycles/scene/integrator.h @@ -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)