EEVEE Next: Motion Blur fixes
Fix motion blur for viewport re-projection and final image renders. Pull Request: https://projects.blender.org/blender/blender/pulls/110114
This commit is contained in:
committed by
Clément Foucault
parent
0af370a62d
commit
9db289924f
@@ -478,7 +478,7 @@ void Film::end_sync()
|
||||
data_.use_reprojection = inst_.sampling.interactive_mode();
|
||||
|
||||
/* Just bypass the reprojection and reset the accumulation. */
|
||||
if (force_disable_reprojection_ && inst_.sampling.is_reset()) {
|
||||
if (inst_.is_viewport() && force_disable_reprojection_ && inst_.sampling.is_reset()) {
|
||||
data_.use_reprojection = false;
|
||||
data_.use_history = false;
|
||||
}
|
||||
|
||||
@@ -197,26 +197,12 @@ void Instance::object_sync(Object *ob)
|
||||
ObjectHandle &ob_handle = sync.sync_object(ob);
|
||||
|
||||
if (partsys_is_visible && ob != DRW_context_state_get()->object_edit) {
|
||||
int sub_key = 1;
|
||||
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
|
||||
if (md->type == eModifierType_ParticleSystem) {
|
||||
ParticleSystem *particle_sys = reinterpret_cast<ParticleSystemModifierData *>(md)->psys;
|
||||
ParticleSettings *part_settings = particle_sys->part;
|
||||
const int draw_as = (part_settings->draw_as == PART_DRAW_REND) ? part_settings->ren_as :
|
||||
part_settings->draw_as;
|
||||
if (draw_as != PART_DRAW_PATH ||
|
||||
!DRW_object_is_visible_psys_in_active_context(ob, particle_sys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectHandle _ob_handle{};
|
||||
_ob_handle.object_key = ObjectKey(ob_handle.object_key.ob, sub_key++);
|
||||
_ob_handle.recalc = particle_sys->recalc;
|
||||
ResourceHandle _res_handle = manager->resource_handle(float4x4(ob->object_to_world));
|
||||
|
||||
sync.sync_curves(ob, _ob_handle, _res_handle, md, particle_sys);
|
||||
}
|
||||
}
|
||||
auto sync_hair =
|
||||
[&](ObjectHandle hair_handle, ModifierData &md, ParticleSystem &particle_sys) {
|
||||
ResourceHandle _res_handle = manager->resource_handle(float4x4(ob->object_to_world));
|
||||
sync.sync_curves(ob, hair_handle, _res_handle, &md, &particle_sys);
|
||||
};
|
||||
foreach_hair_particle_handle(ob, ob_handle, sync_hair);
|
||||
}
|
||||
|
||||
if (object_is_visible) {
|
||||
@@ -282,6 +268,7 @@ void Instance::render_sync()
|
||||
|
||||
begin_sync();
|
||||
DRW_render_object_iter(this, render, depsgraph, object_sync_render);
|
||||
velocity.geometry_steps_fill();
|
||||
end_sync();
|
||||
|
||||
manager->end_sync();
|
||||
|
||||
@@ -71,6 +71,7 @@ void MotionBlurModule::init()
|
||||
* 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]);
|
||||
/* Let the main sync loop handle the current step. */
|
||||
}
|
||||
inst_.set_time(time_steps_[1]);
|
||||
}
|
||||
@@ -141,8 +142,9 @@ void MotionBlurModule::sync()
|
||||
{
|
||||
/* Create max velocity tiles. */
|
||||
PassSimple::Sub &sub = motion_blur_ps_.sub("TilesFlatten");
|
||||
eShaderType shader = inst_.is_viewport() ? MOTION_BLUR_TILE_FLATTEN_VIEWPORT :
|
||||
MOTION_BLUR_TILE_FLATTEN_RENDER;
|
||||
eGPUTextureFormat vector_tx_format = inst_.render_buffers.vector_tx_format();
|
||||
eShaderType shader = vector_tx_format == GPU_RG16F ? MOTION_BLUR_TILE_FLATTEN_RG :
|
||||
MOTION_BLUR_TILE_FLATTEN_RGBA;
|
||||
sub.shader_set(inst_.shaders.static_shader_get(shader));
|
||||
sub.bind_ubo("motion_blur_buf", data_);
|
||||
sub.bind_texture("depth_tx", &render_buffers.depth_tx);
|
||||
@@ -210,9 +212,6 @@ void MotionBlurModule::render(View &view, GPUTexture **input_tx, GPUTexture **ou
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -235,17 +234,23 @@ void MotionBlurModule::render(View &view, GPUTexture **input_tx, GPUTexture **ou
|
||||
|
||||
tile_indirection_buf_.clear_to_zero();
|
||||
|
||||
const bool do_motion_vectors_swizzle = inst_.render_buffers.vector_tx_format() == GPU_RG16F;
|
||||
if (do_motion_vectors_swizzle) {
|
||||
/* Change texture swizzling to avoid complexity in gather pass shader. */
|
||||
GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgrg");
|
||||
}
|
||||
|
||||
inst_.manager->submit(motion_blur_ps_, view);
|
||||
|
||||
if (do_motion_vectors_swizzle) {
|
||||
/* Reset swizzle since this texture might be reused in other places. */
|
||||
GPU_texture_swizzle_set(inst_.render_buffers.vector_tx, "rgba");
|
||||
}
|
||||
|
||||
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_;
|
||||
|
||||
@@ -70,17 +70,14 @@ void RenderBuffers::acquire(int2 extent)
|
||||
depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8);
|
||||
combined_tx.acquire(extent, color_format);
|
||||
|
||||
bool do_vector_render_pass = (enabled_passes & 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;
|
||||
eGPUTextureUsage usage_attachment_read_write = GPU_TEXTURE_USAGE_ATTACHMENT |
|
||||
GPU_TEXTURE_USAGE_SHADER_READ |
|
||||
GPU_TEXTURE_USAGE_SHADER_WRITE;
|
||||
|
||||
/* TODO(fclem): Make vector pass allocation optional if no TAA or motion blur is needed. */
|
||||
vector_tx.acquire(
|
||||
extent, vector_format, usage_attachment_read_write | GPU_TEXTURE_USAGE_MIP_SWIZZLE_VIEW);
|
||||
vector_tx.acquire(extent,
|
||||
vector_tx_format(),
|
||||
usage_attachment_read_write | GPU_TEXTURE_USAGE_MIP_SWIZZLE_VIEW);
|
||||
|
||||
int color_len = data.color_len + data.aovs.color_len;
|
||||
int value_len = data.value_len + data.aovs.value_len;
|
||||
@@ -119,4 +116,14 @@ void RenderBuffers::release()
|
||||
cryptomatte_tx.release();
|
||||
}
|
||||
|
||||
eGPUTextureFormat RenderBuffers::vector_tx_format()
|
||||
{
|
||||
const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get();
|
||||
bool do_vector_render_pass = (enabled_passes & EEVEE_RENDER_PASS_VECTOR) ||
|
||||
(inst_.motion_blur.postfx_enabled() && !inst_.is_viewport());
|
||||
|
||||
/* Only RG16F when only doing only reprojection or motion blur. */
|
||||
return do_vector_render_pass ? GPU_RGBA16F : GPU_RG16F;
|
||||
}
|
||||
|
||||
} // namespace blender::eevee
|
||||
|
||||
@@ -63,6 +63,8 @@ class RenderBuffers {
|
||||
/* Acquires (also ensures) the render buffer before rendering to them. */
|
||||
void acquire(int2 extent);
|
||||
void release();
|
||||
|
||||
eGPUTextureFormat vector_tx_format();
|
||||
};
|
||||
|
||||
} // namespace blender::eevee
|
||||
|
||||
@@ -100,10 +100,10 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
|
||||
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";
|
||||
case MOTION_BLUR_TILE_FLATTEN_RGBA:
|
||||
return "eevee_motion_blur_tiles_flatten_rgba";
|
||||
case MOTION_BLUR_TILE_FLATTEN_RG:
|
||||
return "eevee_motion_blur_tiles_flatten_rg";
|
||||
case DEBUG_SURFELS:
|
||||
return "eevee_debug_surfels";
|
||||
case DISPLAY_PROBE_GRID:
|
||||
|
||||
@@ -72,8 +72,8 @@ enum eShaderType {
|
||||
|
||||
MOTION_BLUR_GATHER,
|
||||
MOTION_BLUR_TILE_DILATE,
|
||||
MOTION_BLUR_TILE_FLATTEN_RENDER,
|
||||
MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
|
||||
MOTION_BLUR_TILE_FLATTEN_RGBA,
|
||||
MOTION_BLUR_TILE_FLATTEN_RG,
|
||||
|
||||
REFLECTION_PROBE_REMAP,
|
||||
REFLECTION_PROBE_UPDATE_IRRADIANCE,
|
||||
|
||||
@@ -404,4 +404,28 @@ void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle)
|
||||
|
||||
/** \} */
|
||||
|
||||
void foreach_hair_particle_handle(Object *ob, ObjectHandle ob_handle, HairHandleCallback callback)
|
||||
{
|
||||
int sub_key = 1;
|
||||
|
||||
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
|
||||
if (md->type == eModifierType_ParticleSystem) {
|
||||
ParticleSystem *particle_sys = reinterpret_cast<ParticleSystemModifierData *>(md)->psys;
|
||||
ParticleSettings *part_settings = particle_sys->part;
|
||||
const int draw_as = (part_settings->draw_as == PART_DRAW_REND) ? part_settings->ren_as :
|
||||
part_settings->draw_as;
|
||||
if (draw_as != PART_DRAW_PATH ||
|
||||
!DRW_object_is_visible_psys_in_active_context(ob, particle_sys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectHandle particle_sys_handle = {0};
|
||||
particle_sys_handle.object_key = ObjectKey(ob_handle.object_key.ob, sub_key++);
|
||||
particle_sys_handle.recalc = particle_sys->recalc;
|
||||
|
||||
callback(particle_sys_handle, *md, *particle_sys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::eevee
|
||||
|
||||
@@ -180,6 +180,9 @@ class SyncModule {
|
||||
void sync_light_probe(Object *ob, ObjectHandle &ob_handle);
|
||||
};
|
||||
|
||||
using HairHandleCallback = FunctionRef<void(ObjectHandle, ModifierData &, ParticleSystem &)>;
|
||||
void foreach_hair_particle_handle(Object *ob, ObjectHandle ob_handle, HairHandleCallback callback);
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::eevee
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "BKE_object.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "DEG_depsgraph_query.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_particle_types.h"
|
||||
#include "DNA_rigidbody_types.h"
|
||||
|
||||
@@ -35,16 +36,17 @@ namespace blender::eevee {
|
||||
|
||||
void VelocityModule::init()
|
||||
{
|
||||
if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) != 0) {
|
||||
if (!inst_.is_viewport() && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) &&
|
||||
!inst_.motion_blur.postfx_enabled())
|
||||
{
|
||||
/* 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;
|
||||
step_sync(STEP_PREVIOUS, initial_time - 1.0f);
|
||||
step_sync(STEP_NEXT, initial_time + 1.0f);
|
||||
|
||||
/* Let the main sync loop handle the current step. */
|
||||
inst_.set_time(initial_time);
|
||||
step_ = STEP_CURRENT;
|
||||
/* Let the main sync loop handle the current step. */
|
||||
}
|
||||
|
||||
/* For viewport, only previous motion is supported.
|
||||
@@ -52,15 +54,44 @@ void VelocityModule::init()
|
||||
next_step_ = inst_.is_viewport() ? STEP_PREVIOUS : STEP_NEXT;
|
||||
}
|
||||
|
||||
static void step_object_sync_render(void *velocity,
|
||||
/* Similar to Instance::object_sync, but only syncs velocity. */
|
||||
static void step_object_sync_render(void *instance,
|
||||
Object *ob,
|
||||
RenderEngine * /*engine*/,
|
||||
Depsgraph * /*depsgraph*/)
|
||||
{
|
||||
ObjectKey object_key(ob);
|
||||
/* NOTE: Dummy resource handle since this will not be used for drawing. */
|
||||
Instance &inst = *reinterpret_cast<Instance *>(instance);
|
||||
|
||||
const bool is_velocity_type = ELEM(
|
||||
ob->type, OB_CURVES, OB_GPENCIL_LEGACY, OB_MESH, OB_POINTCLOUD);
|
||||
const int ob_visibility = DRW_object_visibility_in_active_context(ob);
|
||||
const bool partsys_is_visible = (ob_visibility & OB_VISIBLE_PARTICLES) != 0 &&
|
||||
(ob->type == OB_MESH);
|
||||
const bool object_is_visible = DRW_object_is_renderable(ob) &&
|
||||
(ob_visibility & OB_VISIBLE_SELF) != 0;
|
||||
|
||||
if (!is_velocity_type || (!partsys_is_visible && !object_is_visible)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* NOTE: Dummy resource handle since this won't be used for drawing. */
|
||||
ResourceHandle resource_handle(0);
|
||||
reinterpret_cast<VelocityModule *>(velocity)->step_object_sync(ob, object_key, resource_handle);
|
||||
ObjectHandle &ob_handle = inst.sync.sync_object(ob);
|
||||
|
||||
if (partsys_is_visible) {
|
||||
auto sync_hair =
|
||||
[&](ObjectHandle hair_handle, ModifierData &md, ParticleSystem &particle_sys) {
|
||||
inst.velocity.step_object_sync(
|
||||
ob, hair_handle.object_key, resource_handle, hair_handle.recalc, &md, &particle_sys);
|
||||
};
|
||||
foreach_hair_particle_handle(ob, ob_handle, sync_hair);
|
||||
};
|
||||
|
||||
if (object_is_visible) {
|
||||
inst.velocity.step_object_sync(ob, ob_handle.object_key, resource_handle, ob_handle.recalc);
|
||||
}
|
||||
|
||||
ob_handle.reset_recalc_flag();
|
||||
}
|
||||
|
||||
void VelocityModule::step_sync(eVelocityStep step, float time)
|
||||
@@ -69,7 +100,8 @@ void VelocityModule::step_sync(eVelocityStep step, float time)
|
||||
step_ = step;
|
||||
object_steps_usage[step_] = 0;
|
||||
step_camera_sync();
|
||||
DRW_render_object_iter(this, inst_.render, inst_.depsgraph, step_object_sync_render);
|
||||
DRW_render_object_iter(&inst_, inst_.render, inst_.depsgraph, step_object_sync_render);
|
||||
geometry_steps_fill();
|
||||
}
|
||||
|
||||
void VelocityModule::step_camera_sync()
|
||||
@@ -192,47 +224,45 @@ bool VelocityModule::step_object_sync(Object *ob,
|
||||
return true;
|
||||
}
|
||||
|
||||
void VelocityModule::geometry_steps_fill()
|
||||
{
|
||||
uint dst_ofs = 0;
|
||||
for (VelocityGeometryData &geom : geometry_map.values()) {
|
||||
uint src_len = GPU_vertbuf_get_vertex_len(geom.pos_buf);
|
||||
geom.len = src_len;
|
||||
geom.ofs = dst_ofs;
|
||||
dst_ofs += src_len;
|
||||
}
|
||||
/* TODO(@fclem): Fail gracefully (disable motion blur + warning print) if
|
||||
* `tot_len * sizeof(float4)` is greater than max SSBO size. */
|
||||
geometry_steps[step_]->resize(max_ii(16, dst_ofs));
|
||||
|
||||
for (VelocityGeometryData &geom : geometry_map.values()) {
|
||||
GPU_storagebuf_copy_sub_from_vertbuf(*geometry_steps[step_],
|
||||
geom.pos_buf,
|
||||
geom.ofs * sizeof(float4),
|
||||
0,
|
||||
geom.len * sizeof(float4));
|
||||
}
|
||||
/* Copy back the #VelocityGeometryIndex into #VelocityObjectData which are
|
||||
* indexed using persistent keys (unlike geometries which are indexed by volatile ID). */
|
||||
for (VelocityObjectData &vel : velocity_map.values()) {
|
||||
const VelocityGeometryData &geom = geometry_map.lookup_default(vel.id, VelocityGeometryData());
|
||||
vel.geo.len[step_] = geom.len;
|
||||
vel.geo.ofs[step_] = geom.ofs;
|
||||
/* Avoid reuse. */
|
||||
vel.id = nullptr;
|
||||
}
|
||||
|
||||
geometry_map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves next frame data to previous frame data. Nullify next frame data.
|
||||
* IMPORTANT: This runs AFTER drawing in the viewport (so after `begin_sync()`) but BEFORE drawing
|
||||
* in render mode (so before `begin_sync()`). In viewport the data will be used the next frame.
|
||||
* In Render, moves the next frame data to previous frame data. Nullify next frame data.
|
||||
* In Viewport, the current frame data will be used as previous frame data in the next frame.
|
||||
*/
|
||||
void VelocityModule::step_swap()
|
||||
{
|
||||
{
|
||||
/* Now that vertex buffers are guaranteed to be updated, proceed with
|
||||
* offset computation and copy into the geometry step buffer. */
|
||||
uint dst_ofs = 0;
|
||||
for (VelocityGeometryData &geom : geometry_map.values()) {
|
||||
uint src_len = GPU_vertbuf_get_vertex_len(geom.pos_buf);
|
||||
geom.len = src_len;
|
||||
geom.ofs = dst_ofs;
|
||||
dst_ofs += src_len;
|
||||
}
|
||||
/* TODO(@fclem): Fail gracefully (disable motion blur + warning print) if
|
||||
* `tot_len * sizeof(float4)` is greater than max SSBO size. */
|
||||
geometry_steps[step_]->resize(max_ii(16, dst_ofs));
|
||||
|
||||
for (VelocityGeometryData &geom : geometry_map.values()) {
|
||||
GPU_storagebuf_copy_sub_from_vertbuf(*geometry_steps[step_],
|
||||
geom.pos_buf,
|
||||
geom.ofs * sizeof(float4),
|
||||
0,
|
||||
geom.len * sizeof(float4));
|
||||
}
|
||||
/* Copy back the #VelocityGeometryIndex into #VelocityObjectData which are
|
||||
* indexed using persistent keys (unlike geometries which are indexed by volatile ID). */
|
||||
for (VelocityObjectData &vel : velocity_map.values()) {
|
||||
const VelocityGeometryData &geom = geometry_map.lookup_default(vel.id,
|
||||
VelocityGeometryData());
|
||||
vel.geo.len[step_] = geom.len;
|
||||
vel.geo.ofs[step_] = geom.ofs;
|
||||
/* Avoid reuse. */
|
||||
vel.id = nullptr;
|
||||
}
|
||||
|
||||
geometry_map.clear();
|
||||
}
|
||||
|
||||
auto swap_steps = [&](eVelocityStep step_a, eVelocityStep step_b) {
|
||||
std::swap(object_steps[step_a], object_steps[step_b]);
|
||||
@@ -251,6 +281,7 @@ void VelocityModule::step_swap()
|
||||
};
|
||||
|
||||
if (inst_.is_viewport()) {
|
||||
geometry_steps_fill();
|
||||
/* For viewport we only use the last rendered redraw as previous frame.
|
||||
* We swap current with previous step at the end of a redraw.
|
||||
* We do not support motion blur as it is rendered to avoid conflicting motions
|
||||
|
||||
@@ -140,6 +140,10 @@ class VelocityModule {
|
||||
/* Returns frame time difference between two steps. */
|
||||
float step_time_delta_get(eVelocityStep start, eVelocityStep end) const;
|
||||
|
||||
/* Perform VelocityGeometryData offset computation and copy into the geometry step buffer.
|
||||
* Should be called after all the vertex buffers have been updated by batch cache extraction. */
|
||||
void geometry_steps_fill();
|
||||
|
||||
private:
|
||||
bool object_has_velocity(const Object *ob);
|
||||
bool object_is_deform(const Object *ob);
|
||||
|
||||
@@ -51,7 +51,7 @@ void main()
|
||||
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
|
||||
#ifdef FLATTEN_RG
|
||||
/* imageLoad does not perform the swizzling like sampler does. Do it manually. */
|
||||
motion = motion.xyxy;
|
||||
#endif
|
||||
|
||||
@@ -13,13 +13,13 @@ GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten)
|
||||
.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)
|
||||
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_rg)
|
||||
.do_static_compilation(true)
|
||||
.define("FLATTEN_VIEWPORT")
|
||||
.define("FLATTEN_RG")
|
||||
.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)
|
||||
GPU_SHADER_CREATE_INFO(eevee_motion_blur_tiles_flatten_rgba)
|
||||
.do_static_compilation(true)
|
||||
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_img")
|
||||
.additional_info("eevee_motion_blur_tiles_flatten");
|
||||
|
||||
Reference in New Issue
Block a user