Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClément Foucault <foucault.clem@gmail.com>2021-02-13 00:35:18 +0300
committerClément Foucault <foucault.clem@gmail.com>2021-02-13 00:35:52 +0300
commit000a340afa67a12a38c103e19f7d52ed42c4492e (patch)
treed59ea49aedfba450d7de20240004399e1ee30dc8 /source/blender/draw/engines/eevee/eevee_depth_of_field.c
parentdd2ff266acf999dbb3a37b70162f5ca7c029426a (diff)
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.
Diffstat (limited to 'source/blender/draw/engines/eevee/eevee_depth_of_field.c')
-rw-r--r--source/blender/draw/engines/eevee/eevee_depth_of_field.c1052
1 files changed, 931 insertions, 121 deletions
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_near = DRW_texture_pool_query_2d(
- buffer_size[0], buffer_size[1], down_format, &draw_engine_eevee_type);
- effects->dof_down_far = DRW_texture_pool_query_2d(
- buffer_size[0], buffer_size[1], down_format, &draw_engine_eevee_type);
- effects->dof_coc = DRW_texture_pool_query_2d(
- buffer_size[0], buffer_size[1], GPU_RG16F, &draw_engine_eevee_type);
-
- GPU_framebuffer_ensure_config(&fbl->dof_down_fb,
- {GPU_ATTACHMENT_NONE,
- GPU_ATTACHMENT_TEXTURE(effects->dof_down_near),
- GPU_ATTACHMENT_TEXTURE(effects->dof_down_far),
- GPU_ATTACHMENT_TEXTURE(effects->dof_coc)});
-
- /* Go full 32bits for rendering and reduce the color artifacts. */
- eGPUTextureFormat fb_format = DRW_state_is_image_render() ? GPU_RGBA32F : GPU_RGBA16F;
-
- effects->dof_blur = DRW_texture_pool_query_2d(
- buffer_size[0] * 2, buffer_size[1], fb_format, &draw_engine_eevee_type);
-
- GPU_framebuffer_ensure_config(&fbl->dof_scatter_fb,
- {
- GPU_ATTACHMENT_NONE,
- GPU_ATTACHMENT_TEXTURE(effects->dof_blur),
- });
-
- if (!DRW_state_draw_background()) {
- effects->dof_blur_alpha = DRW_texture_pool_query_2d(
- buffer_size[0] * 2, buffer_size[1], GPU_R32F, &draw_engine_eevee_type);
- GPU_framebuffer_texture_attach(fbl->dof_scatter_fb, effects->dof_blur_alpha, 1, 0);
- }
+ /* Retrieve Near and Far distance */
+ effects->dof_coc_near_dist = -cam->clip_start;
+ effects->dof_coc_far_dist = -cam->clip_end;
/* Parameters */
- /* TODO UI Options */
+ bool is_ortho = cam->type == CAM_ORTHO;
float fstop = cam->dof.aperture_fstop;
float blades = cam->dof.aperture_blades;
float rotation = cam->dof.aperture_rotation;
- float ratio = 1.0f / cam->dof.aperture_ratio;
+ float ratio = 1.0f / max_ff(cam->dof.aperture_ratio, 0.00001f);
float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y);
float focus_dist = BKE_camera_object_dof_distance(camera);
float focal_len = cam->lens;
- const float scale_camera = 0.001f;
+ if (is_ortho) {
+ /* (fclem) A bit of black magic here. I don't know if this is correct. */
+ fstop *= 1.3f;
+ focal_len = 1.0f;
+ sensor = cam->ortho_scale;
+ }
+
+ const float scale_camera = (is_ortho) ? 1.0 : 0.001f;
/* we want radius here for the aperture number */
float aperture = 0.5f * scale_camera * focal_len / fstop;
float focal_len_scaled = scale_camera * focal_len;
@@ -119,93 +245,724 @@ int EEVEE_depth_of_field_init(EEVEE_ViewLayerData *UNUSED(sldata),
sensor_scaled *= rv3d->viewcamtexcofac[0];
}
- effects->dof_params[1] = aperture * fabsf(focal_len_scaled / (focus_dist - focal_len_scaled));
- effects->dof_params[1] *= viewport_size[0] / sensor_scaled;
- effects->dof_params[0] = -focus_dist * effects->dof_params[1];
+ if (ratio > 1.0) {
+ /* If ratio is scaling the bokeh outwards, we scale the aperture so that the gather
+ * kernel size will encompass the maximum axis. */
+ aperture *= ratio;
+ }
+
+ effects->dof_coc_params[1] = -aperture *
+ fabsf(focal_len_scaled / (focus_dist - focal_len_scaled));
+ /* FIXME(fclem) This is broken for vertically fit sensor. */
+ effects->dof_coc_params[1] *= viewport_size[0] / sensor_scaled;
+
+ if ((scene_eval->eevee.flag & SCE_EEVEE_DOF_JITTER) != 0) {
+ effects->dof_jitter_radius = effects->dof_coc_params[1];
+ effects->dof_jitter_focus = focus_dist;
+ effects->dof_jitter_blades = blades;
+
+ int sample_count = EEVEE_temporal_sampling_sample_count_get(scene_eval, stl);
+ sample_count = EEVEE_depth_of_field_sample_count_get(
+ effects, sample_count, &effects->dof_jitter_ring_count);
+
+ if (effects->dof_jitter_ring_count == 0) {
+ effects->dof_jitter_radius = 0.0f;
+ }
+ else {
+ /* Compute a minimal overblur radius to fill the gaps between the samples.
+ * This is just the simplified form of dividing the area of the bokeh
+ * by the number of samples. */
+ float minimal_overblur = 1.0f / sqrtf(sample_count);
+ float user_overblur = scene_eval->eevee.bokeh_overblur / 100.0f;
+
+ effects->dof_coc_params[1] *= minimal_overblur + user_overblur;
+ /* Avoid dilating the shape. Overblur only soften. */
+ effects->dof_jitter_radius -= effects->dof_coc_params[1];
+ }
+ }
+ else {
+ effects->dof_jitter_radius = 0.0f;
+ }
+
+ if (is_ortho) {
+ /* (fclem) A bit of black magic here. Needed to match cycles. */
+ effects->dof_coc_params[1] *= 0.225;
+ }
+
+ effects->dof_coc_params[0] = -focus_dist * effects->dof_coc_params[1];
+
+ effects->dof_bokeh_blades = blades;
+ effects->dof_bokeh_rotation = rotation;
+ effects->dof_bokeh_aniso[0] = min_ff(ratio, 1.0f);
+ effects->dof_bokeh_aniso[1] = min_ff(1.0f / ratio, 1.0f);
+ effects->dof_bokeh_max_size = scene_eval->eevee.bokeh_max_size;
+
+ copy_v2_v2(effects->dof_bokeh_aniso_inv, effects->dof_bokeh_aniso);
+ invert_v2(effects->dof_bokeh_aniso_inv);
+
+ effects->dof_scatter_color_threshold = scene_eval->eevee.bokeh_threshold;
+ effects->dof_scatter_neighbor_max_color = scene_eval->eevee.bokeh_neighbor_max;
+ effects->dof_denoise_factor = clamp_f(scene_eval->eevee.bokeh_denoise_fac, 0.0f, 1.0f);
- effects->dof_bokeh[0] = rotation;
- effects->dof_bokeh[1] = ratio;
- effects->dof_bokeh[2] = scene_eval->eevee.bokeh_max_size;
+ float max_abs_fg_coc, max_abs_bg_coc;
+ if (is_ortho) {
+ max_abs_fg_coc = fabsf(coc_radius_from_camera_depth(true, effects, -cam->clip_start));
+ max_abs_bg_coc = fabsf(coc_radius_from_camera_depth(true, effects, -cam->clip_end));
+ }
+ else {
+ max_abs_fg_coc = fabsf(coc_radius_from_camera_depth(false, effects, -cam->clip_start));
+ /* Background is at infinity so maximum CoC is the limit of the function at -inf. */
+ max_abs_bg_coc = fabsf(effects->dof_coc_params[1]);
+ }
- /* Precompute values to save instructions in fragment shader. */
- effects->dof_bokeh_sides[0] = blades;
- effects->dof_bokeh_sides[1] = blades > 0.0f ? 2.0f * M_PI / blades : 0.0f;
- effects->dof_bokeh_sides[2] = blades / (2.0f * M_PI);
- effects->dof_bokeh_sides[3] = blades > 0.0f ? cosf(M_PI / blades) : 0.0f;
+ float max_coc = max_ff(max_abs_bg_coc, max_abs_fg_coc);
+ /* Clamp with user defined max. */
+ effects->dof_fx_max_coc = min_ff(scene_eval->eevee.bokeh_max_size, max_coc);
+
+ if (effects->dof_fx_max_coc < 0.5f) {
+ return 0;
+ }
return EFFECT_DOF | EFFECT_POST_BUFFER;
}
+ effects->dof_jitter_radius = 0.0f;
+
/* Cleanup to release memory */
- GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_down_fb);
- GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_scatter_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_setup_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_flatten_tiles_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_dilate_tiles_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_reduce_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_reduce_copy_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_gather_fg_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_gather_bg_fb);
+ GPU_FRAMEBUFFER_FREE_SAFE(fbl->dof_scatter_bg_fb);
+ DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_color);
+ DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_coc);
return 0;
}
-void EEVEE_depth_of_field_cache_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata)
+#define WITH_FILTERING (GPU_SAMPLER_MIPMAP | GPU_SAMPLER_FILTER)
+#define NO_FILTERING GPU_SAMPLER_MIPMAP
+#define COLOR_FORMAT fx->dof_color_format
+#define FG_TILE_FORMAT GPU_RGBA16F
+#define BG_TILE_FORMAT GPU_R11F_G11F_B10F
+
+/**
+ * Create bokeh texture.
+ **/
+static void dof_bokeh_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ if ((fx->dof_bokeh_aniso[0] == 1.0f) && (fx->dof_bokeh_aniso[1] == 1.0f) &&
+ (fx->dof_bokeh_blades == 0.0)) {
+ fx->dof_bokeh_gather_lut_tx = NULL;
+ fx->dof_bokeh_scatter_lut_tx = NULL;
+ fx->dof_bokeh_resolve_lut_tx = NULL;
+ return;
+ }
+
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ int res[2] = {DOF_BOKEH_LUT_SIZE, DOF_BOKEH_LUT_SIZE};
+
+ DRW_PASS_CREATE(psl->dof_bokeh, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_bokeh_get();
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_bokeh);
+ DRW_shgroup_uniform_float_copy(grp, "bokehSides", fx->dof_bokeh_blades);
+ DRW_shgroup_uniform_float_copy(grp, "bokehRotation", fx->dof_bokeh_rotation);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ fx->dof_bokeh_gather_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_RG16F, owner);
+ fx->dof_bokeh_scatter_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner);
+ fx->dof_bokeh_resolve_lut_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_bokeh_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_gather_lut_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_scatter_lut_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bokeh_resolve_lut_tx),
+ });
+}
+
+/**
+ * Ouputs halfResColorBuffer and halfResCocBuffer.
+ **/
+static void dof_setup_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
{
- EEVEE_PassList *psl = vedata->psl;
- EEVEE_StorageList *stl = vedata->stl;
- EEVEE_EffectsInfo *effects = stl->effects;
DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
- if ((effects->enabled_effects & EFFECT_DOF) != 0) {
- /** Depth of Field algorithm
- *
- * Overview :
- * - Down-sample the color buffer into 2 buffers weighted with
- * CoC values. Also output CoC into a texture.
- * - Shoot quads for every pixel and expand it depending on the CoC.
- * Do one pass for near Dof and one pass for far Dof.
- * - Finally composite the 2 blurred buffers with the original render.
- */
- DRWShadingGroup *grp;
- struct GPUBatch *quad = DRW_cache_fullscreen_quad_get();
- const bool use_alpha = !DRW_state_draw_background();
-
- DRW_PASS_CREATE(psl->dof_down, DRW_STATE_WRITE_COLOR);
-
- grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_downsample_get(use_alpha),
- psl->dof_down);
- DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &effects->source_buffer);
- DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth);
- DRW_shgroup_uniform_vec2(grp, "nearFar", effects->dof_near_far, 1);
- DRW_shgroup_uniform_vec2(grp, "dofParams", effects->dof_params, 1);
- DRW_shgroup_call(grp, quad, NULL);
-
- DRW_PASS_CREATE(psl->dof_scatter, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL);
-
- /* This create an empty batch of N triangles to be positioned
- * by the vertex shader 0.4ms against 6ms with instancing */
- const float *viewport_size = DRW_viewport_size_get();
- const int sprite_len = ((int)viewport_size[0] / 2) *
- ((int)viewport_size[1] / 2); /* brackets matters */
- grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_scatter_get(use_alpha),
- psl->dof_scatter);
- DRW_shgroup_uniform_texture_ref(grp, "nearBuffer", &effects->dof_down_near);
- DRW_shgroup_uniform_texture_ref(grp, "farBuffer", &effects->dof_down_far);
- DRW_shgroup_uniform_texture_ref(grp, "cocBuffer", &effects->dof_coc);
- DRW_shgroup_uniform_vec4(grp, "bokehParams", effects->dof_bokeh, 2);
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ const float *fullres = DRW_viewport_size_get();
+ int res[2] = {divide_ceil_u(fullres[0], 2), divide_ceil_u(fullres[1], 2)};
+
+ DRW_PASS_CREATE(psl->dof_setup, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_setup_get();
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_setup);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->source_buffer, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "depthBuffer", &dtxl->depth, NO_FILTERING);
+ DRW_shgroup_uniform_vec4_copy(grp, "cocParams", fx->dof_coc_params);
+ DRW_shgroup_uniform_float_copy(grp, "bokehMaxSize", fx->dof_bokeh_max_size);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ fx->dof_half_res_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner);
+ fx->dof_half_res_coc_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_RG16F, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_setup_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_half_res_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_half_res_coc_tx),
+ });
+}
+
+/**
+ * Ouputs min & max coc in each 8x8 half res pixel tiles (so 1/16th of fullres).
+ **/
+static void dof_flatten_tiles_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ const float *fullres = DRW_viewport_size_get();
+ int res[2] = {divide_ceil_u(fullres[0], DOF_TILE_DIVISOR),
+ divide_ceil_u(fullres[1], DOF_TILE_DIVISOR)};
+
+ DRW_PASS_CREATE(psl->dof_flatten_tiles, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_flatten_tiles_get();
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_flatten_tiles);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "halfResCocBuffer", &fx->dof_half_res_coc_tx, NO_FILTERING);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ fx->dof_coc_tiles_fg_tx = DRW_texture_pool_query_2d(UNPACK2(res), FG_TILE_FORMAT, owner);
+ fx->dof_coc_tiles_bg_tx = DRW_texture_pool_query_2d(UNPACK2(res), BG_TILE_FORMAT, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_flatten_tiles_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_coc_tiles_fg_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_coc_tiles_bg_tx),
+ });
+}
+
+/**
+ * Dilates the min & max cocs to cover maximum coc values.
+ * Output format/dimensions should be the same as coc_flatten_pass as they are swapped for
+ * doing multiple dilation passes.
+ **/
+static void dof_dilate_tiles_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ const float *fullres = DRW_viewport_size_get();
+ int res[2] = {divide_ceil_u(fullres[0], DOF_TILE_DIVISOR),
+ divide_ceil_u(fullres[1], DOF_TILE_DIVISOR)};
+
+ DRW_PASS_CREATE(psl->dof_dilate_tiles_minmax, DRW_STATE_WRITE_COLOR);
+ DRW_PASS_CREATE(psl->dof_dilate_tiles_minabs, DRW_STATE_WRITE_COLOR);
+
+ for (int pass = 0; pass < 2; pass++) {
+ DRWPass *drw_pass = (pass == 0) ? psl->dof_dilate_tiles_minmax : psl->dof_dilate_tiles_minabs;
+ GPUShader *sh = EEVEE_shaders_depth_of_field_dilate_tiles_get(pass);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, drw_pass);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_tiles_fg_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_tiles_bg_tx);
+ DRW_shgroup_uniform_bool(grp, "dilateSlightFocus", &fx->dof_dilate_slight_focus, 1);
+ DRW_shgroup_uniform_int(grp, "ringCount", &fx->dof_dilate_ring_count, 1);
+ DRW_shgroup_uniform_int(grp, "ringWidthMultiplier", &fx->dof_dilate_ring_width_multiplier, 1);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+ }
+
+ fx->dof_coc_dilated_tiles_fg_tx = DRW_texture_pool_query_2d(UNPACK2(res), FG_TILE_FORMAT, owner);
+ fx->dof_coc_dilated_tiles_bg_tx = DRW_texture_pool_query_2d(UNPACK2(res), BG_TILE_FORMAT, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_dilate_tiles_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_coc_dilated_tiles_fg_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_coc_dilated_tiles_bg_tx),
+ });
+}
+
+static void dof_dilate_tiles_pass_draw(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ for (int pass = 0; pass < 2; pass++) {
+ DRWPass *drw_pass = (pass == 0) ? psl->dof_dilate_tiles_minmax : psl->dof_dilate_tiles_minabs;
+
+ /* Error introduced by gather center jittering. */
+ const float error_multiplier = 1.0f + 1.0f / (DOF_GATHER_RING_COUNT + 0.5f);
+ int dilation_end_radius = ceilf((fx->dof_fx_max_coc * error_multiplier) / DOF_TILE_DIVISOR);
- DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_len);
+ /* This algorithm produce the exact dilation radius by dividing it in multiple passes. */
+ int dilation_radius = 0;
+ while (dilation_radius < dilation_end_radius) {
+ /* Dilate slight focus only on first iteration. */
+ fx->dof_dilate_slight_focus = (dilation_radius == 0) ? 1 : 0;
- DRW_PASS_CREATE(psl->dof_resolve, DRW_STATE_WRITE_COLOR);
+ int remainder = dilation_end_radius - dilation_radius;
+ /* Do not step over any unvisited tile. */
+ int max_multiplier = dilation_radius + 1;
- grp = DRW_shgroup_create(EEVEE_shaders_depth_of_field_resolve_get(use_alpha),
- psl->dof_resolve);
- DRW_shgroup_uniform_texture_ref(grp, "scatterBuffer", &effects->dof_blur);
- DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &effects->source_buffer);
- DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth);
- DRW_shgroup_uniform_vec2(grp, "nearFar", effects->dof_near_far, 1);
- DRW_shgroup_uniform_vec2(grp, "dofParams", effects->dof_params, 1);
- DRW_shgroup_call(grp, quad, NULL);
+ int ring_count = min_ii(DOF_DILATE_RING_COUNT, ceilf(remainder / (float)max_multiplier));
+ int multiplier = min_ii(max_multiplier, floor(remainder / (float)ring_count));
- if (use_alpha) {
- DRW_shgroup_uniform_texture_ref(grp, "scatterAlphaBuffer", &effects->dof_blur_alpha);
- DRW_shgroup_uniform_bool_copy(grp, "unpremult", DRW_state_is_image_render());
+ dilation_radius += ring_count * multiplier;
+
+ fx->dof_dilate_ring_count = ring_count;
+ fx->dof_dilate_ring_width_multiplier = multiplier;
+
+ GPU_framebuffer_bind(fbl->dof_dilate_tiles_fb);
+ DRW_draw_pass(drw_pass);
+
+ SWAP(GPUFrameBuffer *, fbl->dof_dilate_tiles_fb, fbl->dof_flatten_tiles_fb);
+ SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_bg_tx, fx->dof_coc_tiles_bg_tx);
+ SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_fg_tx, fx->dof_coc_tiles_fg_tx);
}
}
+ /* Swap again so that final textures are dof_coc_dilated_tiles_*_tx. */
+ SWAP(GPUFrameBuffer *, fbl->dof_dilate_tiles_fb, fbl->dof_flatten_tiles_fb);
+ SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_bg_tx, fx->dof_coc_tiles_bg_tx);
+ SWAP(GPUTexture *, fx->dof_coc_dilated_tiles_fg_tx, fx->dof_coc_tiles_fg_tx);
+}
+
+/**
+ * Create mipmaped color & coc textures for gather passes.
+ **/
+static void dof_reduce_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_TextureList *txl,
+ EEVEE_EffectsInfo *fx)
+{
+ const float *fullres = DRW_viewport_size_get();
+
+ /* Divide by 2 because dof_fx_max_coc is in fullres CoC radius and the reduce texture begins at
+ * half resolution. */
+ float max_space_between_sample = fx->dof_fx_max_coc * 0.5f / DOF_GATHER_RING_COUNT;
+
+ int mip_count = max_ii(1, log2_ceil_u(max_space_between_sample));
+
+ fx->dof_reduce_steps = mip_count - 1;
+ /* This ensure the mipmaps are aligned for the needed 4 mip levels.
+ * Starts at 2 because already at half resolution. */
+ int multiple = 2 << (mip_count - 1);
+ int res[2] = {(multiple * divide_ceil_u(fullres[0], multiple)) / 2,
+ (multiple * divide_ceil_u(fullres[1], multiple)) / 2};
+
+ int quater_res[2] = {divide_ceil_u(fullres[0], 4), divide_ceil_u(fullres[1], 4)};
+
+ /* TODO(fclem): Make this dependent of the quality of the gather pass. */
+ fx->dof_scatter_coc_threshold = 4.0f;
+
+ {
+ DRW_PASS_CREATE(psl->dof_downsample, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_downsample_get();
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_downsample);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ fx->dof_downsample_tx = DRW_texture_pool_query_2d(UNPACK2(quater_res), COLOR_FORMAT, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_downsample_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_downsample_tx),
+ });
+ }
+
+ {
+ DRW_PASS_CREATE(psl->dof_reduce_copy, DRW_STATE_WRITE_COLOR);
+
+ const bool is_copy_pass = true;
+ GPUShader *sh = EEVEE_shaders_depth_of_field_reduce_get(is_copy_pass);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_reduce_copy);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "downsampledBuffer", &fx->dof_downsample_tx, NO_FILTERING);
+ DRW_shgroup_uniform_float_copy(grp, "scatterColorThreshold", fx->dof_scatter_color_threshold);
+ DRW_shgroup_uniform_float_copy(
+ grp, "scatterColorNeighborMax", fx->dof_scatter_neighbor_max_color);
+ DRW_shgroup_uniform_float_copy(grp, "scatterCocThreshold", fx->dof_scatter_coc_threshold);
+ DRW_shgroup_uniform_float_copy(grp, "colorNeighborClamping", fx->dof_denoise_factor);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ fx->dof_scatter_src_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R11F_G11F_B10F, owner);
+ }
+
+ {
+ DRW_PASS_CREATE(psl->dof_reduce, DRW_STATE_WRITE_COLOR);
+
+ const bool is_copy_pass = false;
+ GPUShader *sh = EEVEE_shaders_depth_of_field_reduce_get(is_copy_pass);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_reduce);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBuffer", &fx->dof_reduce_input_color_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "cocBuffer", &fx->dof_reduce_input_coc_tx, NO_FILTERING);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+ }
+
+ if (txl->dof_reduced_color) {
+ /* TODO(fclem) In the future, we need to check if mip_count did not change.
+ * For now it's ok as we always define all mip level.*/
+ if (res[0] != GPU_texture_width(txl->dof_reduced_color) ||
+ res[1] != GPU_texture_width(txl->dof_reduced_color)) {
+ DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_color);
+ DRW_TEXTURE_FREE_SAFE(txl->dof_reduced_coc);
+ }
+ }
+
+ if (txl->dof_reduced_color == NULL) {
+ /* Color needs to be signed format here. See note in shader for explanation. */
+ /* Do not use texture pool because of needs mipmaps. */
+ txl->dof_reduced_color = GPU_texture_create_2d(
+ "dof_reduced_color", UNPACK2(res), mip_count, GPU_RGBA16F, NULL);
+ txl->dof_reduced_coc = GPU_texture_create_2d(
+ "dof_reduced_coc", UNPACK2(res), mip_count, GPU_R16F, NULL);
+
+ /* TODO(fclem) Remove once we have immutable storage or when mips are generated on creation. */
+ GPU_texture_generate_mipmap(txl->dof_reduced_color);
+ GPU_texture_generate_mipmap(txl->dof_reduced_coc);
+ }
+
+ GPU_framebuffer_ensure_config(&fbl->dof_reduce_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_color),
+ GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_coc),
+ });
+
+ GPU_framebuffer_ensure_config(&fbl->dof_reduce_copy_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_color),
+ GPU_ATTACHMENT_TEXTURE(txl->dof_reduced_coc),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_scatter_src_tx),
+ });
+}
+
+/**
+ * Do the gather convolution. For each pixels we gather multiple pixels in its neighborhood
+ * depending on the min & max CoC tiles.
+ **/
+static void dof_gather_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_TextureList *txl,
+ EEVEE_EffectsInfo *fx)
+{
+ void *owner = (void *)&EEVEE_depth_of_field_init;
+ const float *fullres = DRW_viewport_size_get();
+ int res[2] = {divide_ceil_u(fullres[0], 2), divide_ceil_u(fullres[1], 2)};
+ int input_size[2];
+ GPU_texture_get_mipmap_size(txl->dof_reduced_color, 0, input_size);
+ float uv_correction_fac[2] = {res[0] / (float)input_size[0], res[1] / (float)input_size[1]};
+ float output_texel_size[2] = {1.0f / res[0], 1.0f / res[1]};
+ const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL);
+
+ {
+ DRW_PASS_CREATE(psl->dof_gather_fg_holefill, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_HOLEFILL, false);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_fg_holefill);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx);
+ DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac);
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ /* Reuse textures from the setup pass. */
+ /* NOTE: We could use the texture pool do that for us but it does not track usage and it might
+ * backfire (it does in practice). */
+ fx->dof_fg_holefill_color_tx = fx->dof_half_res_color_tx;
+ fx->dof_fg_holefill_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_gather_fg_holefill_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx),
+ });
+ }
+ {
+ DRW_PASS_CREATE(psl->dof_gather_fg, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_FOREGROUND, use_bokeh_tx);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_fg);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx);
+ DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac);
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size);
+ if (use_bokeh_tx) {
+ /* Negate to flip bokeh shape. Mimics optical phenomenon. */
+ negate_v2(fx->dof_bokeh_aniso);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso);
+ DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_gather_lut_tx);
+ /* Restore. */
+ negate_v2(fx->dof_bokeh_aniso);
+ }
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ fx->dof_fg_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner);
+ fx->dof_fg_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner);
+ /* Reuse textures from the setup pass. */
+ /* NOTE: We could use the texture pool do that for us but it does not track usage and it might
+ * backfire (it does in practice). */
+ fx->dof_fg_occlusion_tx = fx->dof_half_res_coc_tx;
+
+ /* NOTE: First target is holefill texture so we can use the median filter on it.
+ * See the filter function. */
+ GPU_framebuffer_ensure_config(&fbl->dof_gather_fg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_occlusion_tx),
+ });
+ }
+ {
+ DRW_PASS_CREATE(psl->dof_gather_bg, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_gather_get(DOF_GATHER_BACKGROUND, use_bokeh_tx);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_gather_bg);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBufferBilinear", &txl->dof_reduced_color, WITH_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &txl->dof_reduced_color, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesFgBuffer", &fx->dof_coc_dilated_tiles_fg_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "cocTilesBgBuffer", &fx->dof_coc_dilated_tiles_bg_tx);
+ DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherInputUvCorrection", uv_correction_fac);
+ DRW_shgroup_uniform_vec2_copy(grp, "gatherOutputTexelSize", output_texel_size);
+ if (use_bokeh_tx) {
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso);
+ DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_gather_lut_tx);
+ }
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ fx->dof_bg_color_tx = DRW_texture_pool_query_2d(UNPACK2(res), COLOR_FORMAT, owner);
+ fx->dof_bg_weight_tx = DRW_texture_pool_query_2d(UNPACK2(res), GPU_R16F, owner);
+ /* Reuse, since only used for scatter. Foreground is processed before background. */
+ fx->dof_bg_occlusion_tx = fx->dof_fg_occlusion_tx;
+
+ /* NOTE: First target is holefill texture so we can use the median filter on it.
+ * See the filter function. */
+ GPU_framebuffer_ensure_config(&fbl->dof_gather_bg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_holefill_weight_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bg_occlusion_tx),
+ });
+ }
+}
+
+/**
+ * Filter an input buffer using a median filter to reduce noise.
+ * NOTE: We use the holefill texture as our input to reduce memory usage.
+ * Thus, the holefill pass cannot be filtered.
+ **/
+static void dof_filter_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ DRW_PASS_CREATE(psl->dof_filter, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_filter_get();
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_filter);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "colorBuffer", &fx->dof_fg_holefill_color_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(
+ grp, "weightBuffer", &fx->dof_fg_holefill_weight_tx, NO_FILTERING);
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_filter_fg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_weight_tx),
+ });
+
+ GPU_framebuffer_ensure_config(&fbl->dof_filter_bg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bg_color_tx),
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bg_weight_tx),
+ });
+}
+
+/**
+ * Do the Scatter convolution. A sprite is emited for every 4 pixels but is only expanded if the
+ * pixels are bright enough to be scattered.
+ **/
+static void dof_scatter_pass_init(EEVEE_FramebufferList *fbl,
+ EEVEE_PassList *psl,
+ EEVEE_TextureList *txl,
+ EEVEE_EffectsInfo *fx)
+{
+ int input_size[2], target_size[2];
+ GPU_texture_get_mipmap_size(fx->dof_half_res_color_tx, 0, input_size);
+ GPU_texture_get_mipmap_size(fx->dof_bg_color_tx, 0, target_size);
+ /* Draw a sprite for every four halfres pixels. */
+ int sprite_count = (input_size[0] / 2) * (input_size[1] / 2);
+ float target_texel_size[2] = {1.0f / target_size[0], 1.0f / target_size[1]};
+ const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL);
+
+ {
+ DRW_PASS_CREATE(psl->dof_scatter_fg, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL);
+
+ const bool is_foreground = true;
+ GPUShader *sh = EEVEE_shaders_depth_of_field_scatter_get(is_foreground, use_bokeh_tx);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_scatter_fg);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->dof_scatter_src_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "occlusionBuffer", &fx->dof_fg_occlusion_tx);
+ DRW_shgroup_uniform_vec2_copy(grp, "targetTexelSize", target_texel_size);
+ DRW_shgroup_uniform_int_copy(grp, "spritePerRow", input_size[0] / 2);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso);
+ if (use_bokeh_tx) {
+ /* Negate to flip bokeh shape. Mimics optical phenomenon. */
+ negate_v2(fx->dof_bokeh_aniso_inv);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv);
+ DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_scatter_lut_tx);
+ /* Restore. */
+ negate_v2(fx->dof_bokeh_aniso_inv);
+ }
+ DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_count);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_scatter_fg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_fg_color_tx),
+ });
+ }
+ {
+ DRW_PASS_CREATE(psl->dof_scatter_bg, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL);
+
+ const bool is_foreground = false;
+ GPUShader *sh = EEVEE_shaders_depth_of_field_scatter_get(is_foreground, use_bokeh_tx);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_scatter_bg);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "colorBuffer", &fx->dof_scatter_src_tx, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "cocBuffer", &txl->dof_reduced_coc, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "occlusionBuffer", &fx->dof_bg_occlusion_tx);
+ DRW_shgroup_uniform_vec2_copy(grp, "targetTexelSize", target_texel_size);
+ DRW_shgroup_uniform_int_copy(grp, "spritePerRow", input_size[0] / 2);
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropy", fx->dof_bokeh_aniso);
+ if (use_bokeh_tx) {
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv);
+ DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_scatter_lut_tx);
+ }
+ DRW_shgroup_call_procedural_triangles(grp, NULL, sprite_count);
+
+ GPU_framebuffer_ensure_config(&fbl->dof_scatter_bg_fb,
+ {
+ GPU_ATTACHMENT_NONE,
+ GPU_ATTACHMENT_TEXTURE(fx->dof_bg_color_tx),
+ });
+ }
+}
+
+/**
+ * Recombine the result of the foreground and background processing. Also perform a slight out of
+ * focus blur to improve geometric continuity.
+ **/
+static void dof_recombine_pass_init(EEVEE_FramebufferList *UNUSED(fbl),
+ EEVEE_PassList *psl,
+ EEVEE_EffectsInfo *fx)
+{
+ DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
+ const bool use_bokeh_tx = (fx->dof_bokeh_gather_lut_tx != NULL);
+
+ DRW_PASS_CREATE(psl->dof_resolve, DRW_STATE_WRITE_COLOR);
+
+ GPUShader *sh = EEVEE_shaders_depth_of_field_resolve_get(use_bokeh_tx, fx->dof_hq_slight_focus);
+ DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->dof_resolve);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "fullResColorBuffer", &fx->source_buffer, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref_ex(grp, "fullResDepthBuffer", &dtxl->depth, NO_FILTERING);
+ DRW_shgroup_uniform_texture_ref(grp, "bgColorBuffer", &fx->dof_bg_color_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "bgWeightBuffer", &fx->dof_bg_weight_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "bgTileBuffer", &fx->dof_coc_dilated_tiles_bg_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "fgColorBuffer", &fx->dof_fg_color_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "fgWeightBuffer", &fx->dof_fg_weight_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "holefillColorBuffer", &fx->dof_fg_holefill_color_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "holefillWeightBuffer", &fx->dof_fg_holefill_weight_tx);
+ DRW_shgroup_uniform_texture_ref(grp, "fgTileBuffer", &fx->dof_coc_dilated_tiles_fg_tx);
+ DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
+ DRW_shgroup_uniform_vec4_copy(grp, "cocParams", fx->dof_coc_params);
+ DRW_shgroup_uniform_float_copy(grp, "bokehMaxSize", fx->dof_bokeh_max_size);
+ if (use_bokeh_tx) {
+ DRW_shgroup_uniform_vec2_copy(grp, "bokehAnisotropyInv", fx->dof_bokeh_aniso_inv);
+ DRW_shgroup_uniform_texture_ref(grp, "bokehLut", &fx->dof_bokeh_resolve_lut_tx);
+ }
+ DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
+}
+
+void EEVEE_depth_of_field_cache_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata)
+{
+ EEVEE_TextureList *txl = vedata->txl;
+ EEVEE_FramebufferList *fbl = vedata->fbl;
+ EEVEE_PassList *psl = vedata->psl;
+ EEVEE_StorageList *stl = vedata->stl;
+ EEVEE_EffectsInfo *fx = stl->effects;
+
+ if ((fx->enabled_effects & EFFECT_DOF) != 0) {
+ /* GPU_RGBA16F is sufficient now that all scattered bokeh are premultiplied.
+ * GPU_R11F_G11F_B10F is not enough when lots of scattered sprites are big and offers
+ * relatively small benefits. */
+ fx->dof_color_format = GPU_RGBA16F;
+
+ dof_bokeh_pass_init(fbl, psl, fx);
+ dof_setup_pass_init(fbl, psl, fx);
+ dof_flatten_tiles_pass_init(fbl, psl, fx);
+ dof_dilate_tiles_pass_init(fbl, psl, fx);
+ dof_reduce_pass_init(fbl, psl, txl, fx);
+ dof_gather_pass_init(fbl, psl, txl, fx);
+ dof_filter_pass_init(fbl, psl, fx);
+ dof_scatter_pass_init(fbl, psl, txl, fx);
+ dof_recombine_pass_init(fbl, psl, fx);
+ }
+}
+
+static void dof_recursive_reduce(void *vedata, int UNUSED(level))
+{
+ EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl;
+ EEVEE_EffectsInfo *fx = ((EEVEE_Data *)vedata)->stl->effects;
+
+ fx->dof_reduce_input_color_tx = txl->dof_reduced_color;
+ fx->dof_reduce_input_coc_tx = txl->dof_reduced_coc;
+
+ DRW_draw_pass(psl->dof_reduce);
}
void EEVEE_depth_of_field_draw(EEVEE_Data *vedata)
@@ -214,24 +971,77 @@ void EEVEE_depth_of_field_draw(EEVEE_Data *vedata)
EEVEE_TextureList *txl = vedata->txl;
EEVEE_FramebufferList *fbl = vedata->fbl;
EEVEE_StorageList *stl = vedata->stl;
- EEVEE_EffectsInfo *effects = stl->effects;
+ EEVEE_EffectsInfo *effects = stl->effects; /* TODO(fclem): Because of silly SWAP_BUFFERS. */
+ EEVEE_EffectsInfo *fx = effects;
/* Depth Of Field */
if ((effects->enabled_effects & EFFECT_DOF) != 0) {
- const float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ DRW_stats_group_start("Depth of Field");
+
+ if (fx->dof_bokeh_gather_lut_tx != NULL) {
+ GPU_framebuffer_bind(fbl->dof_bokeh_fb);
+ DRW_draw_pass(psl->dof_bokeh);
+ }
+
+ GPU_framebuffer_bind(fbl->dof_setup_fb);
+ DRW_draw_pass(psl->dof_setup);
+
+ GPU_framebuffer_bind(fbl->dof_flatten_tiles_fb);
+ DRW_draw_pass(psl->dof_flatten_tiles);
- /* Downsample */
- GPU_framebuffer_bind(fbl->dof_down_fb);
- DRW_draw_pass(psl->dof_down);
+ dof_dilate_tiles_pass_draw(fbl, psl, fx);
- /* Scatter */
- GPU_framebuffer_bind(fbl->dof_scatter_fb);
- GPU_framebuffer_clear_color(fbl->dof_scatter_fb, clear_col);
- DRW_draw_pass(psl->dof_scatter);
+ fx->dof_reduce_input_color_tx = fx->dof_half_res_color_tx;
+ fx->dof_reduce_input_coc_tx = fx->dof_half_res_coc_tx;
- /* Resolve */
- GPU_framebuffer_bind(effects->target_buffer);
+ /* First step is just a copy. */
+ GPU_framebuffer_bind(fbl->dof_downsample_fb);
+ DRW_draw_pass(psl->dof_downsample);
+
+ /* First step is just a copy. */
+ GPU_framebuffer_bind(fbl->dof_reduce_copy_fb);
+ DRW_draw_pass(psl->dof_reduce_copy);
+
+ GPU_framebuffer_recursive_downsample(
+ fbl->dof_reduce_fb, fx->dof_reduce_steps, &dof_recursive_reduce, vedata);
+
+ {
+ /* Foreground convolution. */
+ GPU_framebuffer_bind(fbl->dof_gather_fg_fb);
+ DRW_draw_pass(psl->dof_gather_fg);
+
+ GPU_framebuffer_bind(fbl->dof_filter_fg_fb);
+ DRW_draw_pass(psl->dof_filter);
+
+ GPU_framebuffer_bind(fbl->dof_scatter_fg_fb);
+ DRW_draw_pass(psl->dof_scatter_fg);
+ }
+
+ {
+ /* Background convolution. */
+ GPU_framebuffer_bind(fbl->dof_gather_bg_fb);
+ DRW_draw_pass(psl->dof_gather_bg);
+
+ GPU_framebuffer_bind(fbl->dof_filter_bg_fb);
+ DRW_draw_pass(psl->dof_filter);
+
+ GPU_framebuffer_bind(fbl->dof_scatter_bg_fb);
+ DRW_draw_pass(psl->dof_scatter_bg);
+ }
+
+ {
+ /* Holefill convolution. */
+ GPU_framebuffer_bind(fbl->dof_gather_fg_holefill_fb);
+ DRW_draw_pass(psl->dof_gather_fg_holefill);
+
+ /* NOTE: do not filter the holefill pass as we use it as out filter input buffer. */
+ }
+
+ GPU_framebuffer_bind(fx->target_buffer);
DRW_draw_pass(psl->dof_resolve);
+
SWAP_BUFFERS();
+
+ DRW_stats_group_end();
}
}