[Bf-blender-cvs] [84eb8a3a713] pygpu_extensions: EEVEE: Depth of field: New implementation

Clément Foucault noreply at git.blender.org
Fri Feb 12 22:56:30 CET 2021


Commit: 84eb8a3a7134e6d4071ecbc636aca27714b8f4c3
Author: Clément Foucault
Date:   Fri Feb 12 22:35:18 2021 +0100
Branches: pygpu_extensions
https://developer.blender.org/rB84eb8a3a7134e6d4071ecbc636aca27714b8f4c3

EEVEE: Depth of field: New implementation

This is a complete refactor over the old system. The goal was to increase quality
first and then have something more flexible and optimised.

|{F9603145} | {F9603142}|{F9603147}|

This fixes issues we had with the old system which were:
- Too much overdraw (low performance).
- Not enough precision in render targets (hugly color banding/drifting).
- Poor resolution near in-focus regions.
- Wrong support of orthographic views.
- Missing alpha support in viewport.
- Missing bokeh shape inversion on foreground field.
- Issues on some GPUs. (see T72489) (But I'm sure this one will have other issues as well heh...)
- Fix T81092

I chose Unreal's Diaphragm DOF as a reference / goal implementation.
It is well described in the presentation "A Life of a Bokeh" by Guillaume Abadie.
You can check about it here https://epicgames.ent.box.com/s/s86j70iamxvsuu6j35pilypficznec04

Along side the main implementation we provide a way to increase the quality by jittering the
camera position for each sample (the ones specified under the Sampling tab).

The jittering is dividing the actual post processing dof radius so that it fills the undersampling.
The user can still add more overblur to have a noiseless image, but reducing bokeh shape sharpness.

Effect of overblur (left without, right with):
| {F9603122} | {F9603123}|

The actual implementation differs a bit:
- Foreground gather implementation uses the same "ring binning" accumulator as background
  but uses a custom occlusion method. This gives the problem of inflating the foreground elements
  when they are over background or in-focus regions.
  This is was a hard decision but this was preferable to the other method that was giving poor
  opacity masks for foreground and had other more noticeable issues. Do note it is possible
  to improve this part in the future if a better alternative is found.
- Use occlusion texture for foreground. Presentation says it wasn't really needed for them.
- The TAA stabilisation pass is replace by a simple neighborhood clamping at the reduce copy
  stage for simplicity.
- We don't do a brute-force in-focus separate gather pass. Instead we just do the brute force
  pass during resolve. Using the separate pass could be a future optimization if needed but
  might give less precise results.
- We don't use compute shaders at all so shader branching might not be optimal. But performance
  is still way better than our previous implementation.
- We mainly rely on density change to fix all undersampling issues even for foreground (which
  is something the reference implementation is not doing strangely).

Remaining issues (not considered blocking for me):
- Slight defocus stability: Due to slight defocus bruteforce gather using the bare scene color,
  highlights are dilated and make convergence quite slow or imposible when using jittered DOF
  (or gives )
- ~~Slight defocus inflating: There seems to be a 1px inflation discontinuity of the slight focus
  convolution compared to the half resolution. This is not really noticeable if using jittered
  camera.~~ Fixed
- Foreground occlusion approximation is a bit glitchy and gives incorrect result if the
  a defocus foreground element overlaps a farther foreground element. Note that this is easily
  mitigated using the jittered camera position.
|{F9603114}|{F9603115}|{F9603116}|
- Foreground is inflating,  not revealing background. However this avoids some other bugs too
  as discussed previously. Also mitigated with jittered camera position.
|{F9603130}|{F9603129}|
- Sensor vertical fit is still broken (does not match cycles).
- Scattred bokeh shapes can be a bit strange at polygon vertices. This is due to the distance field
  stored in the Bokeh LUT which is not rounded at the edges. This is barely noticeable if the
  shape does not rotate.
- ~~Sampling pattern of the jittered camera position is suboptimal. Could try something like hammersley
  or poisson disc distribution.~~Used hexaweb sampling pattern which is not random but has better
stability and overall coverage.
- Very large bokeh (> 300 px) can exhibit undersampling artifact in gather pass and quite a bit of
  bleeding. But at this size it is preferable to use jittered camera position.

Codewise the changes are pretty much self contained and each pass are well documented.
However the whole pipeline is quite complex to understand from bird's-eye view.

Notes:
- There is the possibility of using arbitrary bokeh texture with this implementation.
  However implementation is a bit involved.
- Gathering max sample count is hardcoded to avoid to deal with shader variations. The actual
  max sample count is already quite high but samples are not evenly distributed due to the
  ring binning method.
- While this implementation does not need 32bit/channel textures to render correctly it does use
  many other textures so actual VRAM usage is higher than previous method for viewport but less
  for render. Textures are reused to avoid many allocations.
- Bokeh LUT computation is fast and done for each redraw because it can be animated. Also the
  texture can be shared with other viewport with different camera settings.

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

M	release/scripts/startup/bl_ui/properties_render.py
M	source/blender/blenloader/intern/versioning_290.c
M	source/blender/draw/CMakeLists.txt
M	source/blender/draw/engines/eevee/eevee_depth_of_field.c
M	source/blender/draw/engines/eevee/eevee_effects.c
M	source/blender/draw/engines/eevee/eevee_engine.c
M	source/blender/draw/engines/eevee/eevee_private.h
M	source/blender/draw/engines/eevee/eevee_render.c
M	source/blender/draw/engines/eevee/eevee_shaders.c
M	source/blender/draw/engines/eevee/eevee_temporal_sampling.c
M	source/blender/draw/engines/eevee/shaders/common_utiltex_lib.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_bokeh_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_dilate_tiles_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_downsample_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_filter_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_flatten_tiles_frag.glsl
D	source/blender/draw/engines/eevee/shaders/effect_dof_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_gather_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_lib.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_reduce_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_resolve_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_scatter_frag.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_scatter_vert.glsl
A	source/blender/draw/engines/eevee/shaders/effect_dof_setup_frag.glsl
D	source/blender/draw/engines/eevee/shaders/effect_dof_vert.glsl
M	source/blender/draw/engines/workbench/shaders/workbench_effect_dof_frag.glsl
M	source/blender/draw/intern/shaders/common_math_lib.glsl
M	source/blender/draw/tests/shaders_test.cc
M	source/blender/makesdna/DNA_scene_defaults.h
M	source/blender/makesdna/DNA_scene_types.h
M	source/blender/makesrna/intern/rna_scene.c

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

diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py
index b326a23b390..3abaa490d02 100644
--- a/release/scripts/startup/bl_ui/properties_render.py
+++ b/release/scripts/startup/bl_ui/properties_render.py
@@ -198,8 +198,15 @@ class RENDER_PT_eevee_depth_of_field(RenderButtonsPanel, Panel):
 
         col = layout.column()
         col.prop(props, "bokeh_max_size")
-        # Not supported yet
-        # col.prop(props, "bokeh_threshold")
+        col.prop(props, "bokeh_threshold")
+        col.prop(props, "bokeh_neighbor_max")
+        col.prop(props, "bokeh_denoise_fac")
+        col.prop(props, "use_bokeh_high_quality_slight_defocus")
+        col.prop(props, "use_bokeh_jittered")
+
+        col = layout.column()
+        col.active = props.use_bokeh_jittered
+        col.prop(props, "bokeh_overblur")
 
 
 class RENDER_PT_eevee_bloom(RenderButtonsPanel, Panel):
diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index 74b3827ffd0..4980d748d0f 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -1733,6 +1733,14 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
    * \note Keep this message at the bottom of the function.
    */
   {
+    if (!DNA_struct_elem_find(fd->filesdna, "SceneEEVEE", "float", "bokeh_overblur")) {
+      LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+        scene->eevee.bokeh_neighbor_max = 10.0f;
+        scene->eevee.bokeh_denoise_fac = 0.75f;
+        scene->eevee.bokeh_overblur = 5.0f;
+      }
+    }
+
     /* Keep this block, even when empty. */
   }
 }
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 9b716eeeed3..e52e30a6c44 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -217,8 +217,18 @@ data_to_c_simple(engines/eevee/shaders/lightprobe_planar_display_vert.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/lookdev_world_frag.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/closure_lit_lib.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/effect_bloom_frag.glsl SRC)
-data_to_c_simple(engines/eevee/shaders/effect_dof_vert.glsl SRC)
-data_to_c_simple(engines/eevee/shaders/effect_dof_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_bokeh_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_dilate_tiles_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_downsample_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_filter_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_flatten_tiles_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_gather_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_lib.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_reduce_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_resolve_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_scatter_frag.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_scatter_vert.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/effect_dof_setup_frag.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/effect_downsample_frag.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/effect_downsample_cube_frag.glsl SRC)
 data_to_c_simple(engines/eevee/shaders/effect_gtao_frag.glsl SRC)
diff --git a/source/blender/draw/engines/eevee/eevee_depth_of_field.c b/source/blender/draw/engines/eevee/eevee_depth_of_field.c
index 92ba526c67c..8c0a44b2c9b 100644
--- a/source/blender/draw/engines/eevee/eevee_depth_of_field.c
+++ b/source/blender/draw/engines/eevee/eevee_depth_of_field.c
@@ -20,6 +20,13 @@
  * \ingroup draw_engine
  *
  * Depth of field post process effect.
+ *
+ * There are 2 methods to achieve this effect.
+ * - The first uses projection matrix offsetting and sample accumulation to give reference quality
+ *   depth of field. But this needs many samples to hide the undersampling.
+ * - The second one is a post-processing based one. It follows the implementation described in
+ *   the presentation "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. There are some
+ *   difference with our actual implementation that prioritize quality.
  */
 
 #include "DRW_render.h"
@@ -40,10 +47,158 @@
 #include "GPU_texture.h"
 #include "eevee_private.h"
 
+#define CAMERA_JITTER_RING_DENSITY 6
+
+static float coc_radius_from_camera_depth(bool is_ortho, EEVEE_EffectsInfo *fx, float camera_depth)
+{
+  float multiplier = fx->dof_coc_params[0];
+  float bias = fx->dof_coc_params[1];
+  if (multiplier == 0.0f || bias == 0.0f) {
+    return 0.0f;
+  }
+  else if (is_ortho) {
+    return (camera_depth + multiplier / bias) * multiplier;
+  }
+  else {
+    return multiplier / camera_depth - bias;
+  }
+}
+
+static float polygon_sides_length(float sides_count)
+{
+  return 2.0 * sin(M_PI / sides_count);
+}
+
+/* Returns intersection ratio between the radius edge at theta and the polygon edge.
+ * Start first corners at theta == 0. */
+static float circle_to_polygon_radius(float sides_count, float theta)
+{
+  /* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide 36). */
+  float side_angle = (2.0f * M_PI) / sides_count;
+  return cosf(side_angle * 0.5f) /
+         cosf(theta - side_angle * floorf((sides_count * theta + M_PI) / (2.0f * M_PI)));
+}
+
+/* Remap input angle to have homogenous spacing of points along a polygon edge.
+ * Expect theta to be in [0..2pi] range. */
+static float circle_to_polygon_angle(float sides_count, float theta)
+{
+  float side_angle = (2.0f * M_PI) / sides_count;
+  float halfside_angle = side_angle * 0.5f;
+  float side = floorf(theta / side_angle);
+  /* Length of segment from center to the middle of polygon side. */
+  float adjacent = circle_to_polygon_radius(sides_count, 0.0f);
+
+  /* This is the relative position of the sample on the polygon half side. */
+  float local_theta = theta - side * side_angle;
+  float ratio = (local_theta - halfside_angle) / halfside_angle;
+
+  float halfside_len = polygon_sides_length(sides_count) * 0.5f;
+  float opposite = ratio * halfside_len;
+
+  /* NOTE: atan(y_over_x) has output range [-M_PI_2..M_PI_2]. */
+  float final_local_theta = atanf(opposite / adjacent);
+
+  return side * side_angle + final_local_theta;
+}
+
+static int dof_jitter_total_sample_count(int ring_density, int ring_count)
+{
+  return ((ring_count * ring_count + ring_count) / 2) * ring_density + 1;
+}
+
+bool EEVEE_depth_of_field_jitter_get(EEVEE_EffectsInfo *fx,
+                                     float r_jitter[2],
+                                     float *r_focus_distance)
+{
+  if (fx->dof_jitter_radius == 0.0f) {
+    return false;
+  }
+
+  int ring_density = CAMERA_JITTER_RING_DENSITY;
+  int ring_count = fx->dof_jitter_ring_count;
+  int sample_count = dof_jitter_total_sample_count(ring_density, ring_count);
+
+  int s = fx->taa_current_sample - 1;
+
+  int ring = 0;
+  int ring_sample_count = 1;
+  int ring_sample = 1;
+
+  s = s * (ring_density - 1);
+  s = s % sample_count;
+
+  int samples_passed = 1;
+  while (s >= samples_passed) {
+    ring++;
+    ring_sample_count = ring * ring_density;
+    ring_sample = s - samples_passed;
+    ring_sample = (ring_sample + 1) % ring_sample_count;
+    samples_passed += ring_sample_count;
+  }
+
+  r_jitter[0] = (float)ring / ring_count;
+  r_jitter[1] = (float)ring_sample / ring_sample_count;
+
+  {
+    /* Bokeh shape parametrisation */
+    float r = r_jitter[0];
+    float T = r_jitter[1] * 2.0f * M_PI;
+
+    if (fx->dof_jitter_blades >= 3.0f) {
+      T = circle_to_polygon_angle(fx->dof_jitter_blades, T);
+      r *= circle_to_polygon_radius(fx->dof_jitter_blades, T);
+    }
+
+    T += fx->dof_bokeh_rotation;
+
+    r_jitter[0] = r * cosf(T);
+    r_jitter[1] = r * sinf(T);
+
+    mul_v2_v2(r_jitter, fx->dof_bokeh_aniso);
+  }
+
+  mul_v2_fl(r_jitter, fx->dof_jitter_radius);
+
+  *r_focus_distance = fx->dof_jitter_focus;
+  return true;
+}
+
+int EEVEE_depth_of_field_sample_count_get(EEVEE_EffectsInfo *fx,
+                                          int sample_count,
+                                          int *r_ring_count)
+{
+  if (fx->dof_jitter_radius == 0.0f) {
+    if (r_ring_count != NULL) {
+      *r_ring_count = 0;
+    }
+    return 1;
+  }
+
+  if (sample_count == TAA_MAX_SAMPLE) {
+    /* Special case for viewport continuous rendering. We clamp to a max sample to avoid the
+     * jittered dof never converging. */
+    sample_count = 1024;
+  }
+  /* Inversion of dof_jitter_total_sample_count. */
+  float x = 2.0f * (sample_count - 1.0f) / CAMERA_JITTER_RING_DENSITY;
+  /* Solving polynomial. We only search positive solution. */
+  float discriminant = 1.0f + 4.0f * x;
+  int ring_count = ceilf(0.5f * (sqrt(discriminant) - 1.0f));
+
+  sample_count = dof_jitter_total_sample_count(CAMERA_JITTER_RING_DENSITY, ring_count);
+
+  if (r_ring_count != NULL) {
+    *r_ring_count = ring_count;
+  }
+  return sample_count;
+}
+
 int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata),
                               EEVEE_Data *vedata,
                               Object *camera)
 {
+  EEVEE_TextureList *txl = vedata->txl;
   EEVEE_StorageList *stl = vedata->stl;
   EEVEE_FramebufferList *fbl = vedata->fbl;
   EEVEE_EffectsInfo *effects = stl->effects;
@@ -57,59 +212,30 @@ int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata),
     RegionView3D *rv3d = draw_ctx->rv3d;
     const float *viewport_size = DRW_viewport_size_get();
 
-    /* Retrieve Near and Far distance */
-    effects->dof_near_far[0] = -cam->clip_start;
-    effects->dof_near_far[1] = -cam->clip_end;
-
-    int buffer_size[2] = {(int)viewport_size[0] / 2, (int)viewport_size[1] / 2};
+    effects->dof_hq_slight_focus = (scene_eval->eevee.flag & SCE_EEVEE_DOF_HQ_SLIGHT_FOCUS) != 0;
 
-    buffer_size[0] = max_ii(1, buffer_size[0]);
-    buffer_size[1] = max_ii(1, buffer_size[1]);
-
-    eGPUTextureFormat down_format = DRW_state_draw_background() ? GPU_R11F_G11F_B10F : GPU_RGBA16F;
-
-    effects->dof_down

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list