EEVEE-Next: Motion Blur new implementation

The new implementation leverage compute shaders to reduce the
number of passes and complexity.

The max blur amount is now detected automatically, replacing the property
in the render panel by a simple checkbox.

The dilation algorithm has also been rewritten from scratch into a 1 pass
algorithm that does the dilation more efficiently and more precisely.

Some differences with the old implementation can be observed in areas with
complex motion.
This commit is contained in:
Clément Foucault 2022-07-27 17:35:10 +02:00
parent 82327ce01d
commit 1e0aa2612c
30 changed files with 1241 additions and 49 deletions

View File

@ -162,6 +162,35 @@ class RENDER_PT_eevee_motion_blur(RenderButtonsPanel, Panel):
col.prop(props, "motion_blur_steps", text="Steps")
class RENDER_PT_eevee_next_motion_blur(RenderButtonsPanel, Panel):
bl_label = "Motion Blur"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw_header(self, context):
scene = context.scene
props = scene.eevee
self.layout.prop(props, "use_motion_blur", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.eevee
layout.active = props.use_motion_blur
col = layout.column()
col.prop(props, "motion_blur_position", text="Position")
col.prop(props, "motion_blur_shutter")
col.separator()
col.prop(props, "motion_blur_depth_scale")
col.prop(props, "motion_blur_steps", text="Steps")
class RENDER_PT_eevee_depth_of_field(RenderButtonsPanel, Panel):
bl_label = "Depth of Field"
bl_options = {'DEFAULT_CLOSED'}
@ -756,6 +785,7 @@ classes = (
RENDER_PT_eevee_film,
RENDER_PT_eevee_next_sampling,
RENDER_PT_eevee_next_motion_blur,
RENDER_PT_eevee_next_film,
RENDER_PT_gpencil,

View File

@ -138,6 +138,7 @@ set(SRC
engines/eevee_next/eevee_film.cc
engines/eevee_next/eevee_instance.cc
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
@ -367,7 +368,12 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl
engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl
engines/eevee_next/shaders/eevee_geom_world_vert.glsl
engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl
engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
engines/eevee_next/shaders/eevee_surf_forward_frag.glsl

View File

@ -45,3 +45,7 @@
#define LIGHTPROBE_FILTER_VIS_GROUP_SIZE 16
#define FILM_GROUP_SIZE 16
#define MOTION_BLUR_GROUP_SIZE 32
#define MOTION_BLUR_DILATE_GROUP_SIZE 512

View File

@ -214,6 +214,11 @@ void Film::init(const int2 &extent, const rcti *output_rect)
/* Filter obsolete passes. */
render_passes &= ~(EEVEE_RENDER_PASS_UNUSED_8 | EEVEE_RENDER_PASS_BLOOM);
if (scene_eevee.flag & SCE_EEVEE_MOTION_BLUR_ENABLED) {
/* Disable motion vector pass if motion blur is enabled. */
render_passes &= ~EEVEE_RENDER_PASS_VECTOR;
}
/* TODO(@fclem): Can't we rely on depsgraph update notification? */
if (assign_if_different(enabled_passes_, render_passes)) {
sampling.reset();
@ -381,7 +386,7 @@ void Film::sync()
DRW_shgroup_uniform_block_ref(grp, "camera_curr", &(*velocity.camera_steps[STEP_CURRENT]));
DRW_shgroup_uniform_block_ref(grp, "camera_next", &(*velocity.camera_steps[step_next]));
DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &rbuffers.depth_tx);
DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &rbuffers.combined_tx);
DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &combined_final_tx_);
DRW_shgroup_uniform_texture_ref(grp, "normal_tx", &rbuffers.normal_tx);
DRW_shgroup_uniform_texture_ref(grp, "vector_tx", &rbuffers.vector_tx);
DRW_shgroup_uniform_texture_ref(grp, "diffuse_light_tx", &rbuffers.diffuse_light_tx);
@ -540,7 +545,7 @@ void Film::update_sample_table()
}
}
void Film::accumulate(const DRWView *view)
void Film::accumulate(const DRWView *view, GPUTexture *combined_final_tx)
{
if (inst_.is_viewport()) {
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
@ -556,6 +561,8 @@ void Film::accumulate(const DRWView *view)
update_sample_table();
combined_final_tx_ = combined_final_tx;
/* Need to update the static references as there could have change from a previous swap. */
weight_src_tx_ = weight_tx_.current();
weight_dst_tx_ = weight_tx_.next();
@ -588,6 +595,8 @@ void Film::display()
GPU_framebuffer_bind(dfbl->default_fb);
GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent));
combined_final_tx_ = inst_.render_buffers.combined_tx;
/* Need to update the static references as there could have change from a previous swap. */
weight_src_tx_ = weight_tx_.current();
weight_dst_tx_ = weight_tx_.next();

View File

@ -50,6 +50,8 @@ class Film {
/** Static reference as SwapChain does not actually move the objects when swapping. */
GPUTexture *combined_src_tx_ = nullptr;
GPUTexture *combined_dst_tx_ = nullptr;
/** Incomming combined buffer with post fx applied (motion blur + depth of field). */
GPUTexture *combined_final_tx_ = nullptr;
/** Weight buffers. Double buffered to allow updating it during accumulation. */
SwapChain<Texture, 2> weight_tx_;
/** Static reference as SwapChain does not actually move the objects when swapping. */
@ -74,7 +76,7 @@ class Film {
void end_sync();
/** Accumulate the newly rendered sample contained in #RenderBuffers and blit to display. */
void accumulate(const DRWView *view);
void accumulate(const DRWView *view, GPUTexture *combined_final_tx);
/** Blit to display. No rendered sample needed. */
void display();

View File

@ -61,6 +61,7 @@ void Instance::init(const int2 &output_res,
camera.init();
film.init(output_res, output_rect);
velocity.init();
motion_blur.init();
main_view.init();
}
@ -93,14 +94,14 @@ void Instance::update_eval_members()
void Instance::begin_sync()
{
materials.begin_sync();
velocity.begin_sync();
velocity.begin_sync(); /* NOTE: Also syncs camera. */
gpencil_engine_enabled = false;
motion_blur.sync();
pipelines.sync();
main_view.sync();
world.sync();
camera.sync();
film.sync();
}
@ -212,14 +213,10 @@ void Instance::render_sample()
sampling.step();
main_view.render();
motion_blur.step();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Interface
* \{ */
void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
{
while (!sampling.finished()) {
@ -259,7 +256,10 @@ void Instance::draw_viewport(DefaultFramebufferList *dfbl)
render_sample();
velocity.step_swap();
if (!sampling.finished_viewport()) {
/* Do not request redraw during viewport animation to lock the framerate to the animation
* playback rate. This is in order to preserve motion blur aspect and also to avoid TAA reset
* that can show flickering. */
if (!sampling.finished_viewport() && !DRW_state_is_playback()) {
DRW_viewport_request_redraw();
}

View File

@ -18,6 +18,7 @@
#include "eevee_camera.hh"
#include "eevee_film.hh"
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
@ -34,6 +35,7 @@ namespace blender::eevee {
*/
class Instance {
friend VelocityModule;
friend MotionBlurModule;
public:
ShaderModule &shaders;
@ -41,6 +43,7 @@ class Instance {
MaterialModule materials;
PipelineModule pipelines;
VelocityModule velocity;
MotionBlurModule motion_blur;
Sampling sampling;
Camera camera;
Film film;
@ -76,6 +79,7 @@ class Instance {
materials(*this),
pipelines(*this),
velocity(*this),
motion_blur(*this),
sampling(*this),
camera(*this),
film(*this),

View File

@ -0,0 +1,262 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2021 Blender Foundation.
*/
/** \file
* \ingroup eevee
*/
// #include "BLI_map.hh"
#include "DEG_depsgraph_query.h"
#include "eevee_instance.hh"
#include "eevee_motion_blur.hh"
// #include "eevee_sampling.hh"
// #include "eevee_shader_shared.hh"
// #include "eevee_velocity.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name MotionBlurModule
*
* \{ */
void MotionBlurModule::init()
{
const Scene *scene = inst_.scene;
enabled_ = (scene->eevee.flag & SCE_EEVEE_MOTION_BLUR_ENABLED) != 0;
if (!enabled_) {
motion_blur_fx_enabled_ = false;
return;
}
/* Take into account the steps needed for fx motion blur. */
int steps_count = max_ii(1, scene->eevee.motion_blur_steps) * 2 + 1;
time_steps_.resize(steps_count);
initial_frame_ = scene->r.cfra;
initial_subframe_ = scene->r.subframe;
frame_time_ = initial_frame_ + initial_subframe_;
shutter_position_ = scene->eevee.motion_blur_position;
shutter_time_ = scene->eevee.motion_blur_shutter;
data_.depth_scale = scene->eevee.motion_blur_depth_scale;
motion_blur_fx_enabled_ = true; /* TODO(fclem): UI option. */
/* Viewport stops here. We only do Post-FX motion blur. */
if (inst_.is_viewport()) {
enabled_ = false;
return;
}
/* Without this there is the possibility of the curve table not being allocated. */
BKE_curvemapping_changed((struct CurveMapping *)&scene->r.mblur_shutter_curve, false);
Vector<float> cdf(CM_TABLE);
Sampling::cdf_from_curvemapping(scene->r.mblur_shutter_curve, cdf);
Sampling::cdf_invert(cdf, time_steps_);
for (float &time : time_steps_) {
time = this->shutter_time_to_scene_time(time);
}
step_id_ = 1;
if (motion_blur_fx_enabled_) {
/* A bit weird but we have to sync the first 2 steps here because the step()
* function is only called after rendering a sample. */
inst_.velocity.step_sync(STEP_PREVIOUS, time_steps_[0]);
inst_.velocity.step_sync(STEP_NEXT, time_steps_[2]);
}
inst_.set_time(time_steps_[1]);
}
/* Runs after rendering a sample. */
void MotionBlurModule::step()
{
if (!enabled_) {
return;
}
if (inst_.sampling.finished()) {
/* Restore original frame number. This is because the render pipeline expects it. */
RE_engine_frame_set(inst_.render, initial_frame_, initial_subframe_);
}
else if (inst_.sampling.do_render_sync()) {
/* Time to change motion step. */
BLI_assert(time_steps_.size() > step_id_ + 2);
step_id_ += 2;
if (motion_blur_fx_enabled_) {
inst_.velocity.step_swap();
inst_.velocity.step_sync(eVelocityStep::STEP_NEXT, time_steps_[step_id_ + 1]);
}
inst_.set_time(time_steps_[step_id_]);
}
}
float MotionBlurModule::shutter_time_to_scene_time(float time)
{
switch (shutter_position_) {
case SCE_EEVEE_MB_START:
/* No offset. */
break;
case SCE_EEVEE_MB_CENTER:
time -= 0.5f;
break;
case SCE_EEVEE_MB_END:
time -= 1.0;
break;
default:
BLI_assert(!"Invalid motion blur position enum!");
break;
}
time *= shutter_time_;
time += frame_time_;
return time;
}
void MotionBlurModule::sync()
{
/* Disable motion blur in viewport when changing camera projection type.
* Avoids really high velocities. */
if (inst_.velocity.camera_changed_projection()) {
motion_blur_fx_enabled_ = false;
}
if (!motion_blur_fx_enabled_) {
return;
}
eGPUSamplerState no_filter = GPU_SAMPLER_DEFAULT;
RenderBuffers &render_buffers = inst_.render_buffers;
{
/* Create max velocity tiles. */
DRW_PASS_CREATE(tiles_flatten_ps_, DRW_STATE_NO_DRAW);
eShaderType shader = (inst_.is_viewport()) ? MOTION_BLUR_TILE_FLATTEN_VIEWPORT :
MOTION_BLUR_TILE_FLATTEN_RENDER;
GPUShader *sh = inst_.shaders.static_shader_get(shader);
DRWShadingGroup *grp = DRW_shgroup_create(sh, tiles_flatten_ps_);
inst_.velocity.bind_resources(grp);
DRW_shgroup_uniform_block(grp, "motion_blur_buf", data_);
DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &render_buffers.depth_tx);
DRW_shgroup_uniform_image_ref(grp, "velocity_img", &render_buffers.vector_tx);
DRW_shgroup_uniform_image_ref(grp, "out_tiles_img", &tiles_tx_);
DRW_shgroup_call_compute_ref(grp, dispatch_flatten_size_);
DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
{
/* Expand max velocity tiles by spreading them in their neighborhood. */
DRW_PASS_CREATE(tiles_dilate_ps_, DRW_STATE_NO_DRAW);
GPUShader *sh = inst_.shaders.static_shader_get(MOTION_BLUR_TILE_DILATE);
DRWShadingGroup *grp = DRW_shgroup_create(sh, tiles_dilate_ps_);
DRW_shgroup_storage_block(grp, "tile_indirection_buf", tile_indirection_buf_);
DRW_shgroup_uniform_image_ref(grp, "in_tiles_img", &tiles_tx_);
DRW_shgroup_call_compute_ref(grp, dispatch_dilate_size_);
DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_STORAGE);
}
{
/* Do the motion blur gather algorithm. */
DRW_PASS_CREATE(gather_ps_, DRW_STATE_NO_DRAW);
GPUShader *sh = inst_.shaders.static_shader_get(MOTION_BLUR_GATHER);
DRWShadingGroup *grp = DRW_shgroup_create(sh, gather_ps_);
inst_.sampling.bind_resources(grp);
DRW_shgroup_uniform_block(grp, "motion_blur_buf", data_);
DRW_shgroup_storage_block(grp, "tile_indirection_buf", tile_indirection_buf_);
DRW_shgroup_uniform_texture_ref_ex(grp, "depth_tx", &render_buffers.depth_tx, no_filter);
DRW_shgroup_uniform_texture_ref_ex(grp, "velocity_tx", &render_buffers.vector_tx, no_filter);
DRW_shgroup_uniform_texture_ref_ex(grp, "in_color_tx", &input_color_tx_, no_filter);
DRW_shgroup_uniform_image_ref(grp, "in_tiles_img", &tiles_tx_);
DRW_shgroup_uniform_image_ref(grp, "out_color_img", &output_color_tx_);
DRW_shgroup_call_compute_ref(grp, dispatch_gather_size_);
DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH);
}
}
void MotionBlurModule::render(GPUTexture **input_tx, GPUTexture **output_tx)
{
if (!motion_blur_fx_enabled_) {
return;
}
const Texture &depth_tx = inst_.render_buffers.depth_tx;
int2 extent = {depth_tx.width(), depth_tx.height()};
int2 tiles_extent = math::divide_ceil(extent, int2(MOTION_BLUR_TILE_SIZE));
if (inst_.is_viewport()) {
float frame_delta = fabsf(inst_.velocity.step_time_delta_get(STEP_PREVIOUS, STEP_CURRENT));
/* Avoid highly disturbing blurs, during navigation with high shutter time. */
if (frame_delta > 0.0f && !DRW_state_is_navigating()) {
/* Rescale motion blur intensity to be shutter time relative and avoid long streak when we
* have frame skipping. Always try to stick to what the render frame would look like. */
data_.motion_scale = float2(shutter_time_ / frame_delta);
}
else {
/* There is no time change. Motion only comes from viewport navigation and object transform.
* Apply motion blur as smoothing and only blur towards last frame. */
data_.motion_scale = float2(1.0f, 0.0f);
if (was_navigating_ != DRW_state_is_navigating()) {
/* Special case for navigation events that only last for one frame (for instance mouse
* scroll for zooming). For this case we have to wait for the next frame before enabling
* the navigation motion blur. */
was_navigating_ = DRW_state_is_navigating();
return;
}
}
was_navigating_ = DRW_state_is_navigating();
/* Change texture swizzling to avoid complexity in gather pass shader. */
GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgrg");
}
else {
data_.motion_scale = float2(1.0f);
}
/* Second motion vector is stored inverted. */
data_.motion_scale.y = -data_.motion_scale.y;
data_.target_size_inv = 1.0f / float2(extent);
data_.push_update();
input_color_tx_ = *input_tx;
output_color_tx_ = *output_tx;
dispatch_flatten_size_ = int3(tiles_extent, 1);
dispatch_dilate_size_ = int3(math::divide_ceil(tiles_extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
dispatch_gather_size_ = int3(math::divide_ceil(extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
DRW_stats_group_start("Motion Blur");
tiles_tx_.acquire(tiles_extent, GPU_RGBA16F);
GPU_storagebuf_clear_to_zero(tile_indirection_buf_);
DRW_draw_pass(tiles_flatten_ps_);
DRW_draw_pass(tiles_dilate_ps_);
DRW_draw_pass(gather_ps_);
tiles_tx_.release();
DRW_stats_group_end();
if (inst_.is_viewport()) {
/* Reset swizzle since this texture might be reused in other places. */
GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgba");
}
/* Swap buffers so that next effect has the right input. */
*input_tx = output_color_tx_;
*output_tx = input_color_tx_;
}
/** \} */
} // namespace blender::eevee

View File

@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation.
*/
/** \file
* \ingroup eevee
*
* Motion blur is done by accumulating scene samples over shutter time.
* Since the number of step is discrete, quite low, and not per pixel randomized,
* we couple this with a post processing motion blur.
*
* The post-fx motion blur is done in two directions, from the previous step and to the next.
*
* For a scene with 3 motion steps, a flat shutter curve and shutter time of 2 frame
* centered on frame we have:
*
* |--------------------|--------------------|
* -1 0 1 Frames
*
* |-------------|-------------|-------------|
* 1 2 3 Motion steps
*
* |------|------|------|------|------|------|
* 0 1 2 4 5 6 7 Time Steps
*
* |-------------| One motion step blurs this range.
* -1 | +1 Objects and geometry steps are recorded here.
* 0 Scene is rendered here.
*
* Since motion step N and N+1 share one time step we reuse it to avoid an extra scene evaluation.
*
* Note that we have to evaluate -1 and +1 time steps before rendering so eval order is -1, +1, 0.
* This is because all GPUBatches from the DRWCache are being free when changing a frame.
*
* For viewport, we only have the current and previous step data to work with. So we center the
* blur on the current frame and extrapolate the motion.
*
* The Post-FX motion blur is based on:
* "A Fast and Stable Feature-Aware Motion Blur Filter"
* by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
*/
#pragma once
#include "BLI_map.hh"
#include "DEG_depsgraph_query.h"
#include "eevee_sampling.hh"
#include "eevee_shader_shared.hh"
#include "eevee_velocity.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name MotionBlur
*
* \{ */
/**
* Manages time-steps evaluations and accumulation Motion blur.
* Also handles Post process motion blur.
*/
class MotionBlurModule {
private:
Instance &inst_;
/**
* Array containing all steps (in scene time) we need to evaluate (not render).
* Only odd steps are rendered. The even ones are evaluated for fx motion blur.
*/
Vector<float> time_steps_;
/** Copy of input frame and sub-frame to restore after render. */
int initial_frame_;
float initial_subframe_;
/** Time of the frame we are rendering. */
float frame_time_;
/** Enum controlling when the shutter opens. See SceneEEVEE.motion_blur_position. */
int shutter_position_;
/** Time in scene frame the shutter is open. Controls the amount of blur. */
float shutter_time_;
/** True if motion blur is enabled as a module. */
bool enabled_ = false;
/** True if motion blur post-fx is enabled. */
float motion_blur_fx_enabled_ = false;
/** True if last viewport redraw state was already in navigation state. */
bool was_navigating_ = false;
int step_id_ = 0;
/** Velocity tiles used to guide and speedup the gather pass. */
TextureFromPool tiles_tx_;
GPUTexture *input_color_tx_ = nullptr;
GPUTexture *output_color_tx_ = nullptr;
DRWPass *tiles_flatten_ps_ = nullptr;
DRWPass *tiles_dilate_ps_ = nullptr;
DRWPass *gather_ps_ = nullptr;
MotionBlurTileIndirectionBuf tile_indirection_buf_;
MotionBlurDataBuf data_;
/** Dispatch size for full-screen passes. */
int3 dispatch_flatten_size_ = int3(0);
int3 dispatch_dilate_size_ = int3(0);
int3 dispatch_gather_size_ = int3(0);
public:
MotionBlurModule(Instance &inst) : inst_(inst){};
~MotionBlurModule(){};
void init();
void step();
void sync();
bool postfx_enabled() const
{
return motion_blur_fx_enabled_;
}
void render(GPUTexture **input_tx, GPUTexture **output_tx);
private:
float shutter_time_to_scene_time(float time);
};
/** \} */
} // namespace blender::eevee

View File

@ -38,7 +38,8 @@ void RenderBuffers::acquire(int2 extent)
depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8);
combined_tx.acquire(extent, color_format);
bool do_vector_render_pass = inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR;
bool do_vector_render_pass = (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) ||
(inst_.motion_blur.postfx_enabled() && !inst_.is_viewport());
/* Only RG16F when only doing only reprojection or motion blur. */
eGPUTextureFormat vector_format = do_vector_render_pass ? GPU_RGBA16F : GPU_RG16F;
/* TODO(fclem): Make vector pass allocation optional if no TAA or motion blur is needed. */

View File

@ -232,7 +232,7 @@ void Sampling::cdf_from_curvemapping(const CurveMapping &curve, Vector<float> &c
BLI_assert(cdf.size() > 1);
cdf[0] = 0.0f;
/* Actual CDF evaluation. */
for (int u : cdf.index_range()) {
for (int u : IndexRange(cdf.size() - 1)) {
float x = (float)(u + 1) / (float)(cdf.size() - 1);
cdf[u + 1] = cdf[u] + BKE_curvemapping_evaluateF(&curve, 0, x);
}

View File

@ -82,6 +82,14 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_film_frag";
case FILM_COMP:
return "eevee_film_comp";
case MOTION_BLUR_GATHER:
return "eevee_motion_blur_gather";
case MOTION_BLUR_TILE_DILATE:
return "eevee_motion_blur_tiles_dilate";
case MOTION_BLUR_TILE_FLATTEN_RENDER:
return "eevee_motion_blur_tiles_flatten_render";
case MOTION_BLUR_TILE_FLATTEN_VIEWPORT:
return "eevee_motion_blur_tiles_flatten_viewport";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";

View File

@ -29,6 +29,11 @@ enum eShaderType {
FILM_FRAG = 0,
FILM_COMP,
MOTION_BLUR_GATHER,
MOTION_BLUR_TILE_DILATE,
MOTION_BLUR_TILE_FLATTEN_RENDER,
MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
MAX_SHADER_TYPE,
};

View File

@ -124,7 +124,7 @@ struct CameraData {
float clip_far;
eCameraType type;
bool initialized;
bool1 initialized;
#ifdef __cplusplus
/* Small constructor to allow detecting new buffers. */
@ -311,6 +311,40 @@ BLI_STATIC_ASSERT_ALIGN(VelocityGeometryIndex, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Motion Blur
* \{ */
#define MOTION_BLUR_TILE_SIZE 32
#define MOTION_BLUR_MAX_TILE 512 /* 16384 / MOTION_BLUR_TILE_SIZE */
struct MotionBlurData {
/** As the name suggests. Used to avoid a division in the sampling. */
float2 target_size_inv;
/** Viewport motion scaling factor. Make blur relative to frame time not render time. */
float2 motion_scale;
/** Depth scaling factor. Avoid blurring background behind moving objects. */
float depth_scale;
float _pad0, _pad1, _pad2;
};
BLI_STATIC_ASSERT_ALIGN(MotionBlurData, 16)
/* For some reasons some GLSL compilers do not like this struct.
* So we declare it as a uint array instead and do indexing ourselves. */
#ifdef __cplusplus
struct MotionBlurTileIndirection {
/**
* Stores indirection to the tile with the highest velocity covering each tile.
* This is stored using velocity in the MSB to be able to use atomicMax operations.
*/
uint prev[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
uint next[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
};
BLI_STATIC_ASSERT_ALIGN(MotionBlurTileIndirection, 16)
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ray-Tracing
* \{ */
@ -375,6 +409,8 @@ using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;
using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
} // namespace blender::eevee
#endif

View File

@ -32,8 +32,7 @@ namespace blender::eevee {
void VelocityModule::init()
{
if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) != 0 &&
true /* TODO(fclem) Motion blur */) {
if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) != 0) {
/* No motion blur and the vector pass was requested. Do the steps sync here. */
const Scene *scene = inst_.scene;
float initial_time = scene->r.cfra + scene->r.subframe;
@ -68,10 +67,12 @@ void VelocityModule::step_camera_sync()
{
inst_.camera.sync();
*camera_steps[step_] = inst_.camera.data_get();
step_time[step_] = inst_.scene->r.cfra + inst_.scene->r.subframe;
/* Fix undefined camera steps when rendering is starting. */
if ((step_ == STEP_CURRENT) && (camera_steps[STEP_PREVIOUS]->initialized == false)) {
*camera_steps[STEP_PREVIOUS] = *static_cast<CameraData *>(camera_steps[step_]);
camera_steps[STEP_PREVIOUS]->initialized = true;
step_time[STEP_PREVIOUS] = step_time[step_];
}
}
@ -216,6 +217,7 @@ void VelocityModule::step_swap()
SWAP(VelocityObjectBuf *, object_steps[step_a], object_steps[step_b]);
SWAP(VelocityGeometryBuf *, geometry_steps[step_a], geometry_steps[step_b]);
SWAP(CameraDataBuf *, camera_steps[step_a], camera_steps[step_b]);
SWAP(float, step_time[step_a], step_time[step_b]);
for (VelocityObjectData &vel : velocity_map.values()) {
vel.obj.ofs[step_a] = vel.obj.ofs[step_b];
@ -242,10 +244,7 @@ void VelocityModule::step_swap()
void VelocityModule::begin_sync()
{
if (inst_.is_viewport()) {
/* Viewport always evaluate current step. */
step_ = STEP_CURRENT;
}
step_ = STEP_CURRENT;
step_camera_sync();
object_steps_usage[step_] = 0;
}
@ -364,6 +363,21 @@ bool VelocityModule::camera_has_motion() const
*camera_steps[STEP_NEXT] != *camera_steps[STEP_CURRENT];
}
bool VelocityModule::camera_changed_projection() const
{
/* Only valid after sync. */
if (inst_.is_viewport()) {
return camera_steps[STEP_PREVIOUS]->type != camera_steps[STEP_CURRENT]->type;
}
/* Cannot happen in render mode since we set the type during the init phase. */
return false;
}
float VelocityModule::step_time_delta_get(eVelocityStep start, eVelocityStep end) const
{
return step_time[end] - step_time[start];
}
/** \} */
} // namespace blender::eevee

View File

@ -56,6 +56,8 @@ class VelocityModule {
int3 object_steps_usage = int3(0);
/** Buffer of all #VelocityIndex used in this frame. Indexed by draw manager resource id. */
VelocityIndexBuf indirection_buf;
/** Frame time at which each steps were evaluated. */
float3 step_time;
/**
* Copies of camera data. One for previous and one for next time step.
@ -78,7 +80,6 @@ class VelocityModule {
}
for (CameraDataBuf *&step_buf : camera_steps) {
step_buf = new CameraDataBuf();
/* */
}
};
@ -112,6 +113,10 @@ class VelocityModule {
void bind_resources(DRWShadingGroup *grp);
bool camera_has_motion() const;
bool camera_changed_projection() const;
/* Returns frame time difference between two steps. */
float step_time_delta_get(eVelocityStep start, eVelocityStep end) const;
private:
bool object_has_velocity(const Object *ob);

View File

@ -79,7 +79,6 @@ void ShadingView::sync()
render_view_ = DRW_view_create_sub(main_view_, viewmat_p, winmat_p);
// dof_.sync(winmat_p, extent_);
// mb_.sync(extent_);
// rt_buffer_opaque_.sync(extent_);
// rt_buffer_refract_.sync(extent_);
// inst_.hiz_back.view_sync(extent_);
@ -132,9 +131,9 @@ void ShadingView::render()
// inst_.lights.debug_draw(view_fb_);
// inst_.shadows.debug_draw(view_fb_);
// GPUTexture *final_radiance_tx = render_post(combined_tx_);
GPUTexture *combined_final_tx = render_postfx(rbufs.combined_tx);
inst_.film.accumulate(sub_view_);
inst_.film.accumulate(sub_view_, combined_final_tx);
rbufs.release();
postfx_tx_.release();
@ -142,21 +141,19 @@ void ShadingView::render()
DRW_stats_group_end();
}
GPUTexture *ShadingView::render_post(GPUTexture *input_tx)
GPUTexture *ShadingView::render_postfx(GPUTexture *input_tx)
{
#if 0
if (!dof_.postfx_enabled() && !mb_.enabled()) {
if (/*!dof_.postfx_enabled() &&*/ !inst_.motion_blur.postfx_enabled()) {
return input_tx;
}
postfx_tx_.acquire(extent_, GPU_RGBA16F);
GPUTexture *velocity_tx = velocity_.view_vectors_get();
GPUTexture *output_tx = postfx_tx_;
/* Swapping is done internally. Actual output is set to the next input. */
dof_.render(depth_tx_, &input_tx, &output_tx);
mb_.render(depth_tx_, velocity_tx, &input_tx, &output_tx);
#endif
// dof_.render(depth_tx_, &input_tx, &output_tx);
inst_.motion_blur.render(&input_tx, &output_tx);
return input_tx;
}

View File

@ -78,7 +78,7 @@ class ShadingView {
void render();
GPUTexture *render_post(GPUTexture *input_tx);
GPUTexture *render_postfx(GPUTexture *input_tx);
private:
void update_view();

View File

@ -0,0 +1,116 @@
/**
* Dilate motion vector tiles until we covered maximum velocity.
* Outputs the largest intersecting motion vector in the neighboorhod.
*
*/
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl)
#define DEBUG_BYPASS_DILATION 0
struct MotionRect {
ivec2 bottom_left;
ivec2 extent;
};
MotionRect compute_motion_rect(ivec2 tile, vec2 motion)
{
#if DEBUG_BYPASS_DILATION
return MotionRect(tile, ivec2(1));
#endif
/* Ceil to number of tile touched.*/
ivec2 point1 = tile + ivec2(sign(motion) * ceil(abs(motion) / float(MOTION_BLUR_TILE_SIZE)));
ivec2 point2 = tile;
ivec2 max_point = max(point1, point2);
ivec2 min_point = min(point1, point2);
/* Clamp to bounds. */
max_point = min(max_point, imageSize(in_tiles_img) - 1);
min_point = max(min_point, ivec2(0));
MotionRect rect;
rect.bottom_left = min_point;
rect.extent = 1 + max_point - min_point;
return rect;
}
struct MotionLine {
/** Origin of the line. */
vec2 origin;
/** Normal to the line direction. */
vec2 normal;
};
MotionLine compute_motion_line(ivec2 tile, vec2 motion)
{
vec2 dir = safe_normalize(motion);
MotionLine line;
line.origin = vec2(tile);
/* Rotate 90° Counter-Clockwise. */
line.normal = vec2(-dir.y, dir.x);
return line;
}
bool is_inside_motion_line(ivec2 tile, MotionLine motion_line)
{
#if DEBUG_BYPASS_DILATION
return true;
#endif
/* NOTE: Everything in is tile unit. */
float dist = point_line_projection_dist(vec2(tile), motion_line.origin, motion_line.normal);
/* In order to be conservative and for simplicity, we use the tiles bounding circles.
* Consider that both the tile and the line have bouding radius of M_SQRT1_2. */
return abs(dist) < M_SQRT2;
}
void main()
{
ivec2 src_tile = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(src_tile, imageSize(in_tiles_img)))) {
return;
}
vec4 max_motion = imageLoad(in_tiles_img, src_tile);
MotionPayload payload_prv = motion_blur_tile_indirection_pack_payload(max_motion.xy, src_tile);
MotionPayload payload_nxt = motion_blur_tile_indirection_pack_payload(max_motion.zw, src_tile);
if (true) {
/* Rectangular area (in tiles) where the motion vector spreads. */
MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.xy);
MotionLine motion_line = compute_motion_line(src_tile, max_motion.xy);
/* Do a conservative rasterization of the line of the motion vector line. */
for (int x = 0; x < motion_rect.extent.x; x++) {
for (int y = 0; y < motion_rect.extent.y; y++) {
ivec2 tile = motion_rect.bottom_left + ivec2(x, y);
if (is_inside_motion_line(tile, motion_line)) {
motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_PREV, tile, payload_prv);
/* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
* the motion next so that weighting in gather pass is better. */
motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_NEXT, tile, payload_nxt);
}
}
}
}
if (true) {
MotionPayload payload = motion_blur_tile_indirection_pack_payload(max_motion.zw, src_tile);
/* Rectangular area (in tiles) where the motion vector spreads. */
MotionRect motion_rect = compute_motion_rect(src_tile, max_motion.zw);
MotionLine motion_line = compute_motion_line(src_tile, max_motion.zw);
/* Do a conservative rasterization of the line of the motion vector line. */
for (int x = 0; x < motion_rect.extent.x; x++) {
for (int y = 0; y < motion_rect.extent.y; y++) {
ivec2 tile = motion_rect.bottom_left + ivec2(x, y);
if (is_inside_motion_line(tile, motion_line)) {
motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_NEXT, tile, payload_nxt);
/* FIXME: This is a bit weird, but for some reason, we need the store the same vector in
* the motion next so that weighting in gather pass is better. */
motion_blur_tile_indirection_store(tile_indirection_buf, MOTION_PREV, tile, payload_prv);
}
}
}
}
}

View File

@ -0,0 +1,103 @@
/**
* Shaders that down-sample velocity buffer into squared tile of MB_TILE_DIVISOR pixels wide.
* Outputs the largest motion vector in the tile area.
* Also perform velocity resolve to speedup the convolution pass.
*
* Based on:
* A Fast and Stable Feature-Aware Motion Blur Filter
* by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
*
* Adapted from G3D Innovation Engine implementation.
*/
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
shared uint payload_prev;
shared uint payload_next;
shared vec2 max_motion_prev;
shared vec2 max_motion_next;
/* Store velocity magnitude in the MSB and thread id in the LSB. */
uint pack_payload(vec2 motion, uvec2 thread_id)
{
/* NOTE: We clamp max velocity to 16k pixels. */
return (min(uint(ceil(length(motion))), 0xFFFFu) << 16u) | (thread_id.y << 8) | thread_id.x;
}
/* Return thread index from the payload. */
uvec2 unpack_payload(uint payload)
{
return uvec2(payload & 0xFFu, (payload >> 8) & 0xFFu);
}
void main()
{
if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) {
payload_prev = 0u;
payload_next = 0u;
}
barrier();
uint local_payload_prev = 0u;
uint local_payload_next = 0u;
vec2 local_max_motion_prev;
vec2 local_max_motion_next;
ivec2 texel = min(ivec2(gl_GlobalInvocationID.xy), imageSize(velocity_img) - 1);
vec2 render_size = vec2(imageSize(velocity_img).xy);
vec2 uv = (vec2(texel) + 0.5) / render_size;
float depth = texelFetch(depth_tx, texel, 0).r;
vec4 motion = velocity_resolve(imageLoad(velocity_img, texel), uv, depth);
#ifdef FLATTEN_VIEWPORT
/* imageLoad does not perform the swizzling like sampler does. Do it manually. */
motion = motion.xyxy;
#endif
/* Store resolved velocity to speedup the gather pass. Out of bounds writes are ignored.
* Unfortunately, we cannot convert to pixel space here since it is also used by TAA and the
* motion blur needs to remain optional. */
imageStore(velocity_img, ivec2(gl_GlobalInvocationID.xy), velocity_pack(motion));
/* Clip velocity to viewport bounds (in NDC space). */
vec2 line_clip;
line_clip.x = line_unit_square_intersect_dist_safe(uv * 2.0 - 1.0, motion.xy * 2.0);
line_clip.y = line_unit_square_intersect_dist_safe(uv * 2.0 - 1.0, -motion.zw * 2.0);
motion *= min(line_clip, vec2(1.0)).xxyy;
/* Convert to pixel space. Note this is only for velocity tiles. */
motion *= render_size.xyxy;
/* Rescale to shutter relative motion for viewport. */
motion *= motion_blur_buf.motion_scale.xxyy;
uint sample_payload_prev = pack_payload(motion.xy, gl_LocalInvocationID.xy);
if (local_payload_prev < sample_payload_prev) {
local_payload_prev = sample_payload_prev;
local_max_motion_prev = motion.xy;
}
uint sample_payload_next = pack_payload(motion.zw, gl_LocalInvocationID.xy);
if (local_payload_next < sample_payload_next) {
local_payload_next = sample_payload_next;
local_max_motion_next = motion.zw;
}
/* Compare the local payload with the other threads. */
atomicMax(payload_prev, local_payload_prev);
atomicMax(payload_next, local_payload_next);
barrier();
/* Need to broadcast the result to another thread in order to issue a unique write. */
if (all(equal(unpack_payload(payload_prev), gl_LocalInvocationID.xy))) {
max_motion_prev = local_max_motion_prev;
}
if (all(equal(unpack_payload(payload_next), gl_LocalInvocationID.xy))) {
max_motion_next = local_max_motion_next;
}
barrier();
if (all(equal(gl_LocalInvocationID.xy, uvec2(0)))) {
ivec2 tile_co = ivec2(gl_WorkGroupID.xy);
imageStore(out_tiles_img, tile_co, vec4(max_motion_prev, max_motion_next));
}
}

View File

@ -0,0 +1,221 @@
/**
* Perform two gather blur in the 2 motion blur directions
* Based on:
* A Fast and Stable Feature-Aware Motion Blur Filter
* by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
*
* With modification from the presentation:
* Next Generation Post Processing in Call of Duty Advanced Warfare
* by Jorge Jimenez
*/
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl)
const int gather_sample_count = 8;
/* Converts uv velocity into pixel space. Assumes velocity_tx is the same resolution as the
* target post-fx framebuffer. */
vec4 motion_blur_sample_velocity(sampler2D velocity_tx, vec2 uv)
{
/* We can load velocity without velocity_resolve() since we resovled during the flatten pass. */
vec4 velocity = velocity_unpack(texture(velocity_tx, uv));
return velocity * vec2(textureSize(velocity_tx, 0)).xyxy * motion_blur_buf.motion_scale.xxyy;
}
vec2 spread_compare(float center_motion_length, float sample_motion_length, float offset_length)
{
return saturate(vec2(center_motion_length, sample_motion_length) - offset_length + 1.0);
}
vec2 depth_compare(float center_depth, float sample_depth)
{
vec2 depth_scale = vec2(-motion_blur_buf.depth_scale, motion_blur_buf.depth_scale);
return saturate(0.5 + depth_scale * (sample_depth - center_depth));
}
/* Kill contribution if not going the same direction. */
float dir_compare(vec2 offset, vec2 sample_motion, float sample_motion_length)
{
if (sample_motion_length < 0.5) {
return 1.0;
}
return (dot(offset, sample_motion) > 0.0) ? 1.0 : 0.0;
}
/* Return background (x) and foreground (y) weights. */
vec2 sample_weights(float center_depth,
float sample_depth,
float center_motion_length,
float sample_motion_length,
float offset_length)
{
/* Classify foreground/background. */
vec2 depth_weight = depth_compare(center_depth, sample_depth);
/* Weight if sample is overlapping or under the center pixel. */
vec2 spread_weight = spread_compare(center_motion_length, sample_motion_length, offset_length);
return depth_weight * spread_weight;
}
struct Accumulator {
vec4 fg;
vec4 bg;
/** x: Background, y: Foreground, z: dir. */
vec3 weight;
};
void gather_sample(vec2 screen_uv,
float center_depth,
float center_motion_len,
vec2 offset,
float offset_len,
const bool next,
inout Accumulator accum)
{
vec2 sample_uv = screen_uv - offset * motion_blur_buf.target_size_inv;
vec4 sample_vectors = motion_blur_sample_velocity(velocity_tx, sample_uv);
vec2 sample_motion = (next) ? sample_vectors.zw : sample_vectors.xy;
float sample_motion_len = length(sample_motion);
float sample_depth = texture(depth_tx, sample_uv).r;
vec4 sample_color = textureLod(in_color_tx, sample_uv, 0.0);
sample_depth = get_view_z_from_depth(sample_depth);
vec3 weights;
weights.xy = sample_weights(
center_depth, sample_depth, center_motion_len, sample_motion_len, offset_len);
weights.z = dir_compare(offset, sample_motion, sample_motion_len);
weights.xy *= weights.z;
accum.fg += sample_color * weights.y;
accum.bg += sample_color * weights.x;
accum.weight += weights;
}
void gather_blur(vec2 screen_uv,
vec2 center_motion,
float center_depth,
vec2 max_motion,
float ofs,
const bool next,
inout Accumulator accum)
{
float center_motion_len = length(center_motion);
float max_motion_len = length(max_motion);
/* Tile boundaries randomization can fetch a tile where there is less motion than this pixel.
* Fix this by overriding the max_motion. */
if (max_motion_len < center_motion_len) {
max_motion_len = center_motion_len;
max_motion = center_motion;
}
if (max_motion_len < 0.5) {
return;
}
int i;
float t, inc = 1.0 / float(gather_sample_count);
for (i = 0, t = ofs * inc; i < gather_sample_count; i++, t += inc) {
gather_sample(screen_uv,
center_depth,
center_motion_len,
max_motion * t,
max_motion_len * t,
next,
accum);
}
if (center_motion_len < 0.5) {
return;
}
for (i = 0, t = ofs * inc; i < gather_sample_count; i++, t += inc) {
/* Also sample in center motion direction.
* Allow recovering motion where there is conflicting
* motion between foreground and background. */
gather_sample(screen_uv,
center_depth,
center_motion_len,
center_motion * t,
center_motion_len * t,
next,
accum);
}
}
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(depth_tx, 0).xy);
if (!in_texture_range(texel, depth_tx)) {
return;
}
/* Data of the center pixel of the gather (target). */
float center_depth = get_view_z_from_depth(texelFetch(depth_tx, texel, 0).r);
vec4 center_motion = motion_blur_sample_velocity(velocity_tx, uv);
vec4 center_color = textureLod(in_color_tx, uv, 0.0);
float noise_offset = sampling_rng_1D_get(SAMPLING_TIME);
/** TODO(fclem) Blue noise. */
vec2 rand = vec2(interlieved_gradient_noise(vec2(gl_GlobalInvocationID.xy), 0, noise_offset),
interlieved_gradient_noise(vec2(gl_GlobalInvocationID.xy), 1, noise_offset));
/* Randomize tile boundary to avoid ugly discontinuities. Randomize 1/4th of the tile.
* Note this randomize only in one direction but in practice it's enough. */
rand.x = rand.x * 2.0 - 1.0;
ivec2 tile = (texel + ivec2(rand.x * float(MOTION_BLUR_TILE_SIZE) * 0.25)) /
MOTION_BLUR_TILE_SIZE;
tile = clamp(tile, ivec2(0), imageSize(in_tiles_img) - 1);
/* NOTE: Tile velocity is already in pixel space and with correct zw sign. */
vec4 max_motion;
/* Load dilation result from the indirection table. */
ivec2 tile_prev;
motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_PREV, tile, tile_prev);
max_motion.xy = imageLoad(in_tiles_img, tile_prev).xy;
ivec2 tile_next;
motion_blur_tile_indirection_load(tile_indirection_buf, MOTION_NEXT, tile, tile_next);
max_motion.zw = imageLoad(in_tiles_img, tile_next).zw;
Accumulator accum;
accum.weight = vec3(0.0, 0.0, 1.0);
accum.bg = vec4(0.0);
accum.fg = vec4(0.0);
/* First linear gather. time = [T - delta, T] */
gather_blur(uv, center_motion.xy, center_depth, max_motion.xy, rand.y, false, accum);
/* Second linear gather. time = [T, T + delta] */
gather_blur(uv, center_motion.zw, center_depth, max_motion.zw, rand.y, true, accum);
#if 1 /* Own addition. Not present in reference implementation. */
/* Avoid division by 0.0. */
float w = 1.0 / (50.0 * float(gather_sample_count) * 4.0);
accum.bg += center_color * w;
accum.weight.x += w;
/* NOTE: In Jimenez's presentation, they used center sample.
* We use background color as it contains more information for foreground
* elements that have not enough weights.
* Yield better blur in complex motion. */
center_color = accum.bg / accum.weight.x;
#endif
/* Merge background. */
accum.fg += accum.bg;
accum.weight.y += accum.weight.x;
/* Balance accumulation for failed samples.
* We replace the missing foreground by the background. */
float blend_fac = saturate(1.0 - accum.weight.y / accum.weight.z);
vec4 out_color = (accum.fg / accum.weight.z) + center_color * blend_fac;
#if 0 /* For debugging. */
out_color.rgb = out_color.ggg;
out_color.rg += max_motion.xy;
#endif
imageStore(out_color_img, texel, out_color);
}

View File

@ -0,0 +1,48 @@
/* -------------------------------------------------------------------- */
/** \name Tile indirection packing
* \{ */
#define MotionPayload uint
/* Store velocity magnitude in the MSB to be able to use it with atomicMax operations. */
MotionPayload motion_blur_tile_indirection_pack_payload(vec2 motion, uvec2 payload)
{
/* NOTE: Clamp to 16383 pixel velocity. After that, it is tile position that determine the tile
* to dilate over. */
uint velocity = min(uint(ceil(length(motion))), 0x3FFFu);
/* Designed for 512x512 tiles max. */
return (velocity << 18u) | ((payload.x & 0x1FFu) << 9u) | (payload.y & 0x1FFu);
}
/* Return thread index. */
ivec2 motion_blur_tile_indirection_pack_payload(uint data)
{
return ivec2((data >> 9u) & 0x1FFu, data & 0x1FFu);
}
uint motion_blur_tile_indirection_index(uint motion_step, uvec2 tile)
{
uint index = tile.x;
index += tile.y * MOTION_BLUR_MAX_TILE;
index += motion_step * MOTION_BLUR_MAX_TILE * MOTION_BLUR_MAX_TILE;
return index;
}
#define MOTION_PREV 0u
#define MOTION_NEXT 1u
#define motion_blur_tile_indirection_store(table_, step_, tile, payload_) \
if (true) { \
uint index = motion_blur_tile_indirection_index(step_, tile); \
atomicMax(table_[index], payload_); \
}
#define motion_blur_tile_indirection_load(table_, step_, tile_, result_) \
if (true) { \
uint index = motion_blur_tile_indirection_index(step_, tile_); \
result_ = motion_blur_tile_indirection_pack_payload(table_[index]); \
}
/** \} */

View File

@ -0,0 +1,104 @@
/**
* Sampling data accessors and random number generators.
* Also contains some sample mapping functions.
**/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Sampling data.
*
* Return a random values from Low Discrepency Sequence in [0..1) range.
* This value is uniform (constant) for the whole scene sample.
* You might want to couple it with a noise function.
* \{ */
#ifdef EEVEE_SAMPLING_DATA
float sampling_rng_1D_get(const eSamplingDimension dimension)
{
return sampling_buf.dimensions[dimension];
}
vec2 sampling_rng_2D_get(const eSamplingDimension dimension)
{
return vec2(sampling_buf.dimensions[dimension], sampling_buf.dimensions[dimension + 1u]);
}
vec3 sampling_rng_3D_get(const eSamplingDimension dimension)
{
return vec3(sampling_buf.dimensions[dimension],
sampling_buf.dimensions[dimension + 1u],
sampling_buf.dimensions[dimension + 2u]);
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Random Number Generators.
* \{ */
/* Interlieved gradient noise by Jorge Jimenez
* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
* Seeding found by Epic Game. */
float interlieved_gradient_noise(vec2 pixel, float seed, float offset)
{
pixel += seed * (vec2(47, 17) * 0.695);
return fract(offset + 52.9829189 * fract(0.06711056 * pixel.x + 0.00583715 * pixel.y));
}
/* From: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html */
float van_der_corput_radical_inverse(uint bits)
{
#if 0 /* Reference */
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
#else
bits = bitfieldReverse(bits);
#endif
/* Same as dividing by 0x100000000. */
return float(bits) * 2.3283064365386963e-10;
}
vec2 hammersley_2d(float i, float sample_count)
{
vec2 rand;
rand.x = i / sample_count;
rand.y = van_der_corput_radical_inverse(uint(i));
return rand;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Distribution mapping.
*
* Functions mapping input random numbers to sampling shapes (i.e: hemisphere).
* \{ */
/* Given 2 random number in [0..1] range, return a random unit disk sample. */
vec2 sample_disk(vec2 noise)
{
float angle = noise.x * M_2PI;
return vec2(cos(angle), sin(angle)) * sqrt(noise.y);
}
/* This transform a 2d random sample (in [0..1] range) to a sample located on a cylinder of the
* same range. This is because the sampling functions expect such a random sample which is
* normally precomputed. */
vec3 sample_cylinder(vec2 rand)
{
float theta = rand.x;
float phi = (rand.y - 0.5) * M_2PI;
float cos_phi = cos(phi);
float sin_phi = sqrt(1.0 - sqr(cos_phi)) * sign(phi);
return vec3(theta, cos_phi, sin_phi);
}
/** \} */

View File

@ -73,7 +73,7 @@ void main()
nodetree_surface();
// float noise_offset = sampling_rng_1D_get(sampling_buf, SAMPLING_TRANSPARENCY);
// float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY);
float noise_offset = 0.5;
float random_threshold = hashed_alpha_threshold(1.0, noise_offset, g_data.P);
@ -84,7 +84,7 @@ void main()
#endif
#ifdef MAT_VELOCITY
out_velocity = velocity_surface(interp.P + motion.prev, interp.P, interp.P - motion.next);
out_velocity = velocity_surface(interp.P + motion.prev, interp.P, interp.P + motion.next);
out_velocity = velocity_pack(out_velocity);
#endif
}

View File

@ -2,8 +2,6 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_camera_lib.glsl)
#ifdef VELOCITY_CAMERA
vec4 velocity_pack(vec4 data)
{
return data * 0.01;
@ -14,6 +12,8 @@ vec4 velocity_unpack(vec4 data)
return data * 100.0;
}
#ifdef VELOCITY_CAMERA
/**
* Given a triple of position, compute the previous and next motion vectors.
* Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy).
@ -24,7 +24,15 @@ vec4 velocity_surface(vec3 P_prv, vec3 P, vec3 P_nxt)
vec2 prev_uv = project_point(camera_prev.persmat, P_prv).xy;
vec2 curr_uv = project_point(camera_curr.persmat, P).xy;
vec2 next_uv = project_point(camera_next.persmat, P_nxt).xy;
/* Fix issue with perspective division. */
if (any(isnan(prev_uv))) {
prev_uv = curr_uv;
}
if (any(isnan(next_uv))) {
next_uv = curr_uv;
}
/* NOTE: We output both vectors in the same direction so we can reuse the same vector
* with rgrg swizzle in viewport. */
vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv);
/* Convert NDC velocity to UV velocity */
motion *= 0.5;
@ -45,7 +53,8 @@ vec4 velocity_background(vec3 vV)
vec2 prev_uv = project_point(camera_prev.winmat, V).xy;
vec2 curr_uv = project_point(camera_curr.winmat, V).xy;
vec2 next_uv = project_point(camera_next.winmat, V).xy;
/* NOTE: We output both vectors in the same direction so we can reuse the same vector
* with rgrg swizzle in viewport. */
vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv);
/* Convert NDC velocity to UV velocity */
motion *= 0.5;
@ -53,16 +62,8 @@ vec4 velocity_background(vec3 vV)
return motion;
}
/**
* Load and resolve correct velocity as some pixels might still not have correct
* motion data for performance reasons.
* Returns motion vector in render UV space.
*/
vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
vec4 velocity_resolve(vec4 vector, vec2 uv, float depth)
{
vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(vector_tx, 0).xy);
vec4 vector = texelFetch(vector_tx, texel, 0);
if (vector.x == VELOCITY_INVALID) {
bool is_background = (depth == 1.0);
if (is_background) {
@ -79,6 +80,18 @@ vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
return velocity_unpack(vector);
}
/**
* Load and resolve correct velocity as some pixels might still not have correct
* motion data for performance reasons.
* Returns motion vector in render UV space.
*/
vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth)
{
vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(vector_tx, 0).xy);
vec4 vector = texelFetch(vector_tx, texel, 0);
return velocity_resolve(vector, uv, depth);
}
#endif
#ifdef MAT_VELOCITY

View File

@ -12,8 +12,9 @@ GPU_SHADER_CREATE_INFO(eevee_shared)
.typedef_source("eevee_shader_shared.hh");
GPU_SHADER_CREATE_INFO(eevee_sampling_data)
.define("EEVEE_SAMPLING_DATA")
.additional_info("eevee_shared")
.uniform_buf(14, "SamplingData", "sampling_buf");
.storage_buf(14, Qualifier::READ, "SamplingData", "sampling_buf");
/** \} */

View File

@ -0,0 +1,44 @@
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten)
.local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
.additional_info("eevee_shared", "draw_view", "eevee_velocity_camera")
.uniform_buf(4, "MotionBlurData", "motion_blur_buf")
.sampler(0, ImageType::DEPTH_2D, "depth_tx")
.image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_tiles_img")
.compute_source("eevee_motion_blur_flatten_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_viewport)
.do_static_compilation(true)
.define("FLATTEN_VIEWPORT")
.image(0, GPU_RG16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_img")
.additional_info("eevee_motion_blur_tiles_flatten");
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_render)
.do_static_compilation(true)
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_img")
.additional_info("eevee_motion_blur_tiles_flatten");
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_dilate)
.do_static_compilation(true)
.local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
.additional_info("eevee_shared")
/* NOTE: See MotionBlurTileIndirection. */
.storage_buf(0, Qualifier::READ_WRITE, "uint", "tile_indirection_buf[]")
.image(1, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_img")
.compute_source("eevee_motion_blur_dilate_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_motion_blur_gather)
.do_static_compilation(true)
.local_group_size(MOTION_BLUR_GROUP_SIZE, MOTION_BLUR_GROUP_SIZE)
.additional_info("eevee_shared", "draw_view", "eevee_sampling_data")
.uniform_buf(4, "MotionBlurData", "motion_blur_buf")
.sampler(0, ImageType::DEPTH_2D, "depth_tx")
.sampler(1, ImageType::FLOAT_2D, "velocity_tx")
.sampler(2, ImageType::FLOAT_2D, "in_color_tx")
/* NOTE: See MotionBlurTileIndirection. */
.storage_buf(0, Qualifier::READ, "uint", "tile_indirection_buf[]")
.image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_tiles_img")
.image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_color_img")
.compute_source("eevee_motion_blur_gather_comp.glsl");

View File

@ -1,6 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef GPU_SHADER
# pragma once
# include "GPU_shader.h"
# include "GPU_shader_shared_utils.h"

View File

@ -10,6 +10,11 @@ float point_plane_projection_dist(vec3 line_origin, vec3 plane_origin, vec3 plan
return dot(plane_normal, plane_origin - line_origin);
}
float point_line_projection_dist(vec2 point, vec2 line_origin, vec2 line_normal)
{
return dot(line_normal, line_origin - point);
}
float line_plane_intersect_dist(vec3 line_origin,
vec3 line_direction,
vec3 plane_origin,
@ -103,6 +108,25 @@ float line_unit_box_intersect_dist_safe(vec3 line_origin, vec3 line_direction)
return line_unit_box_intersect_dist(line_origin, safe_line_direction);
}
/**
* Same as line_unit_box_intersect_dist but for 2D case.
*/
float line_unit_square_intersect_dist(vec2 line_origin, vec2 line_direction)
{
vec2 first_plane = (vec2(1.0) - line_origin) / line_direction;
vec2 second_plane = (vec2(-1.0) - line_origin) / line_direction;
vec2 farthest_plane = max(first_plane, second_plane);
return min_v2(farthest_plane);
}
float line_unit_square_intersect_dist_safe(vec2 line_origin, vec2 line_direction)
{
vec2 safe_line_direction = max(vec2(1e-8), abs(line_direction)) *
select(vec2(1.0), -vec2(1.0), lessThan(line_direction, vec2(0.0)));
return line_unit_square_intersect_dist(line_origin, safe_line_direction);
}
/**
* Returns clipping distance (intersection with the nearest plane) with the given axis-aligned
* bound box along \a line_direction.

View File

@ -456,6 +456,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/basic/shaders/infos/basic_depth_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_film_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_vfx_info.hh