[Bf-blender-cvs] [02767cf142a] cycles-x: Fix Cycles X shadow catcher artifacts

Sergey Sharybin noreply at git.blender.org
Thu Jun 17 13:53:09 CEST 2021


Commit: 02767cf142aa572e14d96245514599678165eb7d
Author: Sergey Sharybin
Date:   Thu Jun 17 11:22:55 2021 +0200
Branches: cycles-x
https://developer.blender.org/rB02767cf142aa572e14d96245514599678165eb7d

Fix Cycles X shadow catcher artifacts

Were caused by matte object appearing in the division process.

This change makes it so the matte object is removed from the
combined pass before division, which solves artifacts caused
by anti-aliasing (the matte "bleeds" into the shadow catcher
and produces artifacts around the rim).

Additionally made certain parts more explicit and better
documented.

Test file: {F10177353}

| Before      | After       |
| {F10177355} | {F10177358} |

Differential Revision: https://developer.blender.org/D11632

===================================================================

M	intern/cycles/kernel/kernel_accumulate.h
M	intern/cycles/kernel/kernel_film.h
M	intern/cycles/kernel/kernel_passes.h
M	intern/cycles/kernel/kernel_types.h

===================================================================

diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h
index a23658b28f2..3dff2464e15 100644
--- a/intern/cycles/kernel/kernel_accumulate.h
+++ b/intern/cycles/kernel/kernel_accumulate.h
@@ -365,25 +365,31 @@ ccl_device void kernel_accum_adaptive_buffer(INTEGRATOR_STATE_CONST_ARGS,
 /* Accumulate contribution to the Shadow Catcher pass.
  *
  * Returns truth if the contribution is fully handled here and is not to be added to the other
- * passes (like combined, adaptive sampling, denoising passes). */
+ * passes (like combined, adaptive sampling). */
 
 ccl_device bool kernel_accum_shadow_catcher(INTEGRATOR_STATE_CONST_ARGS,
                                             const float3 contribution,
                                             ccl_global float *ccl_restrict buffer)
 {
+  if (!kernel_data.integrator.has_shadow_catcher) {
+    return false;
+  }
+
+  kernel_assert(kernel_data.film.pass_shadow_catcher != PASS_UNUSED);
+  kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
   /* Matte pass. */
-  if (kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED) {
-    if (kernel_shadow_catcher_is_matte_path(INTEGRATOR_STATE_PASS)) {
-      kernel_write_pass_float3(buffer + kernel_data.film.pass_shadow_catcher_matte, contribution);
-    }
+  if (kernel_shadow_catcher_is_matte_path(INTEGRATOR_STATE_PASS)) {
+    kernel_write_pass_float3(buffer + kernel_data.film.pass_shadow_catcher_matte, contribution);
+    /* NOTE: Accumulate the combined pass and to the samples count pass, so that the adaptive
+     * sampling is based on how noisy the combined pass is as if there were no catchers in the
+     * scene. */
   }
 
   /* Shadow catcher pass. */
-  if (kernel_data.film.pass_shadow_catcher != PASS_UNUSED) {
-    if (kernel_shadow_catcher_is_object_pass(INTEGRATOR_STATE_PASS)) {
-      kernel_write_pass_float3(buffer + kernel_data.film.pass_shadow_catcher, contribution);
-      return true;
-    }
+  if (kernel_shadow_catcher_is_object_pass(INTEGRATOR_STATE_PASS)) {
+    kernel_write_pass_float3(buffer + kernel_data.film.pass_shadow_catcher, contribution);
+    return true;
   }
 
   return false;
@@ -394,23 +400,30 @@ ccl_device bool kernel_accum_shadow_catcher_transparent(INTEGRATOR_STATE_CONST_A
                                                         const float transparent,
                                                         ccl_global float *ccl_restrict buffer)
 {
+  if (!kernel_data.integrator.has_shadow_catcher) {
+    return false;
+  }
+
+  kernel_assert(kernel_data.film.pass_shadow_catcher != PASS_UNUSED);
+  kernel_assert(kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED);
+
   /* Matte pass. */
-  if (kernel_data.film.pass_shadow_catcher_matte != PASS_UNUSED) {
-    if (kernel_shadow_catcher_is_matte_path(INTEGRATOR_STATE_PASS)) {
-      kernel_write_pass_float4(
-          buffer + kernel_data.film.pass_shadow_catcher_matte,
-          make_float4(contribution.x, contribution.y, contribution.z, transparent));
-    }
+  if (kernel_shadow_catcher_is_matte_path(INTEGRATOR_STATE_PASS)) {
+    kernel_write_pass_float4(
+        buffer + kernel_data.film.pass_shadow_catcher_matte,
+        make_float4(contribution.x, contribution.y, contribution.z, transparent));
+    /* NOTE: Accumulate the combined pass and to the samples count pass, so that the adaptive
+     * sampling is based on how noisy the combined pass is as if there were no catchers in the
+     * scene. */
   }
 
   /* Shadow catcher pass. */
-  if (kernel_data.film.pass_shadow_catcher != PASS_UNUSED) {
-    if (kernel_shadow_catcher_is_object_pass(INTEGRATOR_STATE_PASS)) {
-      kernel_write_pass_float4(
-          buffer + kernel_data.film.pass_shadow_catcher,
-          make_float4(contribution.x, contribution.y, contribution.z, transparent));
-      return true;
-    }
+  if (kernel_shadow_catcher_is_object_pass(INTEGRATOR_STATE_PASS)) {
+    /* NOTE: The transparency of the shadow catcher pass is ignored. It is not needed for the
+     * calculation and the alpha channel of the pass contains numbers of samples contributed to a
+     * pixel of the pass. */
+    kernel_write_pass_float3(buffer + kernel_data.film.pass_shadow_catcher, contribution);
+    return true;
   }
 
   return false;
diff --git a/intern/cycles/kernel/kernel_film.h b/intern/cycles/kernel/kernel_film.h
index 5f375b93644..c84957d38c1 100644
--- a/intern/cycles/kernel/kernel_film.h
+++ b/intern/cycles/kernel/kernel_film.h
@@ -299,26 +299,31 @@ ccl_device_inline void film_get_pass_pixel_shadow(const KernelFilmConvert *ccl_r
  */
 
 ccl_device_inline float4
-film_calculate_shadow_catcher(const KernelFilmConvert *ccl_restrict kfilm_convert,
-                              ccl_global const float *ccl_restrict buffer)
+film_calculate_shadow_catcher_denoised(const KernelFilmConvert *ccl_restrict kfilm_convert,
+                                       ccl_global const float *ccl_restrict buffer)
 {
-  /* For the shadow catcher pass we divide combined pass by the shadow catcher.
-   *
-   * The non-obvious trick here is that we add matte pass to the shadow catcher, so that we
-   * avoid division by zero. This solves artifacts around edges of the artificial object.
-   *
-   * Another trick we do here is to alpha-over the pass on top of white. and ignore the alpha.
-   * This way using transparent film to render artificial objects will be easy to be combined
-   * with a backdrop. */
+  kernel_assert(kfilm_convert->pass_shadow_catcher != PASS_UNUSED);
 
   float scale, scale_exposure;
   film_get_scale_and_scale_exposure(kfilm_convert, buffer, &scale, &scale_exposure);
 
+  ccl_global const float *in_catcher = buffer + kfilm_convert->pass_shadow_catcher;
+
+  const float3 pixel = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]) * scale_exposure;
+
+  return make_float4(pixel.x, pixel.y, pixel.z, 1.0f);
+}
+
+ccl_device_inline float4
+film_calculate_shadow_catcher(const KernelFilmConvert *ccl_restrict kfilm_convert,
+                              ccl_global const float *ccl_restrict buffer)
+{
+  /* For the shadow catcher pass we divide combined pass by the shadow catcher.
+   * Note that denoised shadow catcher pass contains value which only needs ot be scaled (but not
+   * to be calculated as division). */
+
   if (kfilm_convert->is_denoised) {
-    kernel_assert(kfilm_convert->pass_shadow_catcher != PASS_UNUSED);
-    ccl_global const float *in_catcher = buffer + kfilm_convert->pass_shadow_catcher;
-    const float3 pixel = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]) * scale_exposure;
-    return make_float4(pixel.x, pixel.y, pixel.z, 1.0f);
+    return film_calculate_shadow_catcher_denoised(kfilm_convert, buffer);
   }
 
   kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED);
@@ -330,23 +335,35 @@ film_calculate_shadow_catcher(const KernelFilmConvert *ccl_restrict kfilm_conver
   ccl_global const float *in_catcher = buffer + kfilm_convert->pass_shadow_catcher;
   ccl_global const float *in_matte = buffer + kfilm_convert->pass_shadow_catcher_matte;
 
-  const float3 color_catcher = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]) *
-                               scale_exposure;
+  /* If there is no shadow catcher object in this pixel, there is no modification of the light
+   * needed, so return one. */
+  const float num_samples = in_catcher[3];
+  if (num_samples == 0.0f) {
+    return one_float4();
+  }
 
-  const float3 color_combined = make_float3(in_combined[0], in_combined[1], in_combined[2]) *
-                                scale_exposure;
+  /* No scaling needed. The integration works in way that number of samples in the combined and
+   * shadow catcher passes are the same, and exposure is cancelled during the division. */
+  const float3 color_catcher = make_float3(in_catcher[0], in_catcher[1], in_catcher[2]);
+  const float3 color_combined = make_float3(in_combined[0], in_combined[1], in_combined[2]);
+  const float3 color_matte = make_float3(in_matte[0], in_matte[1], in_matte[2]);
 
-  const float3 color_matte = make_float3(in_matte[0], in_matte[1], in_matte[2]) * scale_exposure;
+  /* Need to ignore contribution of the matte object when doing division (otherwise there will be
+   * artifacts caused by anti-aliasing). Since combined pass is used for adaptive sampling and need
+   * to contain matte objects, we subtrack matte objects contribution here. This is the same as if
+   * the matte objects were not accumulated to the combined pass. */
+  const float3 combined_no_matte = color_combined - color_matte;
 
-  const float transparency = in_combined[3] * scale;
-  const float alpha = saturate(1.0f - transparency);
+  const float3 shadow_catcher = safe_divide_color(combined_no_matte, color_catcher);
 
-  const float3 shadow_catcher = safe_divide_even_color(color_combined,
-                                                       color_catcher + color_matte);
+  const float scale = film_get_scale(kfilm_convert, buffer);
+  const float transparency = in_combined[3] * scale;
+  const float alpha = film_transparency_to_alpha(transparency);
 
-  /* Restore pre-multipled nature of the color, avoiding artifacts on the edges.
-   * Makes sense since the division of premultiplied color's "removes" alpha from the
-   * result. */
+  /* Alpha-over on white using transparency of the combined pass. This allows to eliminate
+   * artifacts which are happenning on an edge of a shadow catcher when using transparent film.
+   * Note that we treat shadow catcher as straight alpha here because alpha got cancelled out
+   * during the division. */
   const float3 pixel = (1.0f - alpha) * one_float3() + alpha * shadow_catcher;
 
   return make_float4(pixel.x, pixel.y, pixel.z, 1.0f);
diff --git a/intern/cycles/kernel/kernel_passes.h b/intern/cycles/kernel/kernel_passes.h
index 5c640d32a84..f9aff28df2b 100644
--- a/intern/cycles/kernel/kernel_passes.h
+++ b/intern/cycles/kernel/kernel_passes.h
@@ -126,24 +126,30 @@ ccl_device_inline void kernel_write_denoising_features(
 
 #ifdef __SHADOW_CATCHER__
 
-/* Write transparency to the matte pass at a bounce off the shadow catcher object (this is where
- * the

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list