Cycles: add support for rendering deformation motion blur from Alembic caches.

This patch adds the ability to render motion blur from Alembic caches.
The motion blur data is derived from a velocity attribute whose name has
to be defined by the user through the MeshSequenceCache modifier, with a
default value of ".velocities", which is the standard name in Alembic
for the velocity property, although other software may ignore it and
write velocity with their own naming convention (e.g. "v" in Houdini).

Furthermore, a property was added to define how the velocity vectors
are interpreted with regard to time : frame or second. "Frame"
means that the velocity is already scaled by the time step and we do not
need to modify it for it to look proper. "Second" means that the unit
the velocity was measured in is in seconds and so has to be scaled by
some time step computed here as being the time between two frames (1 /
FPS, which would be typical for a simulation). This appears to be
common, and is the default behavior.

Another property was added to control the scale of the velocity to
further modify the look of the motion blur.

Reviewed By: brecht, sybren

Differential Revision: https://developer.blender.org/D2388
This commit is contained in:
Kévin Dietrich 2020-08-03 03:28:04 +02:00
parent 396d0b5cd0
commit b5dcf74636
Notes: blender-bot 2023-02-14 08:06:38 +01:00
Referenced by issue #86654, Bad motion blur from alembic animation import in v2.92. Works as expected in v2.83.13
Referenced by issue #79568, EEVEE Film transparent not working
11 changed files with 432 additions and 3 deletions

View File

@ -923,6 +923,73 @@ static void create_subd_mesh(Scene *scene,
/* Sync */
static BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob, BL::Scene b_scene)
{
BL::Object::modifiers_iterator b_mod;
for (b_ob.modifiers.begin(b_mod); b_mod != b_ob.modifiers.end(); ++b_mod) {
if (!b_mod->is_a(&RNA_MeshSequenceCacheModifier)) {
continue;
}
BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(*b_mod);
if (MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) {
return mesh_cache;
}
}
return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
}
static void sync_mesh_cached_velocities(BL::Object &b_ob,
BL::Scene b_scene,
Scene *scene,
Mesh *mesh)
{
if (scene->need_motion() == Scene::MOTION_NONE)
return;
BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob, b_scene);
if (!b_mesh_cache) {
return;
}
/* Find or add attribute */
float3 *P = &mesh->verts[0];
Attribute *attr_mP = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
if (!attr_mP) {
attr_mP = mesh->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
}
if (!MeshSequenceCacheModifier_read_velocity_get(&b_mesh_cache.ptr)) {
return;
}
const size_t numverts = mesh->verts.size();
if (b_mesh_cache.vertex_velocities.length() != numverts) {
return;
}
/* Only export previous and next frame, we don't have any in between data. */
float motion_times[2] = {-1.0f, 1.0f};
for (int step = 0; step < 2; step++) {
const float relative_time = motion_times[step] * scene->motion_shutter_time() * 0.5f;
float3 *mP = attr_mP->data_float3() + step * numverts;
BL::MeshSequenceCacheModifier::vertex_velocities_iterator vvi;
int i = 0;
for (b_mesh_cache.vertex_velocities.begin(vvi); vvi != b_mesh_cache.vertex_velocities.end();
++vvi, ++i) {
mP[i] = P[i] + get_float3(vvi->velocity()) * relative_time;
}
}
}
static void sync_mesh_fluid_motion(BL::Object &b_ob, Scene *scene, Mesh *mesh)
{
if (scene->need_motion() == Scene::MOTION_NONE)
@ -1002,6 +1069,9 @@ void BlenderSync::sync_mesh(BL::Depsgraph b_depsgraph,
}
}
/* cached velocities (e.g. from alembic archive) */
sync_mesh_cached_velocities(b_ob, b_depsgraph.scene(), scene, mesh);
/* mesh fluid motion mantaflow */
sync_mesh_fluid_motion(b_ob, scene, mesh);
@ -1023,6 +1093,12 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
return;
}
/* Cached motion blur already exported. */
BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob, b_scene);
if (mesh_cache) {
return;
}
/* Skip if no vertices were exported. */
size_t numverts = mesh->verts.size();
if (numverts == 0) {

View File

@ -61,6 +61,8 @@ static void cache_file_init_data(ID *id)
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(cache_file, id));
cache_file->scale = 1.0f;
cache_file->velocity_unit = CACHEFILE_VELOCITY_UNIT_SECOND;
BLI_strncpy(cache_file->velocity_name, ".velocities", sizeof(cache_file->velocity_name));
}
static void cache_file_copy_data(Main *UNUSED(bmain),

View File

@ -22,9 +22,11 @@
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_brush_types.h"
#include "DNA_cachefile_types.h"
#include "DNA_constraint_types.h"
#include "DNA_genfile.h"
#include "DNA_gpencil_modifier_types.h"
@ -427,5 +429,28 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
/* Initialise additional velocity parameter for CacheFiles. */
if (!DNA_struct_elem_find(
fd->filesdna, "MeshSeqCacheModifierData", "float", "velocity_scale")) {
for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) {
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_MeshSequenceCache) {
MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md;
mcmd->velocity_scale = 1.0f;
mcmd->vertex_velocities = NULL;
mcmd->num_vertices = 0;
}
}
}
}
if (!DNA_struct_elem_find(fd->filesdna, "CacheFile", "char", "velocity_unit")) {
for (CacheFile *cache_file = bmain->cachefiles.first; cache_file != NULL;
cache_file = cache_file->id.next) {
BLI_strncpy(cache_file->velocity_name, ".velocities", sizeof(cache_file->velocity_name));
cache_file->velocity_unit = CACHEFILE_VELOCITY_UNIT_SECOND;
}
}
}
}

View File

@ -7348,6 +7348,9 @@ void uiTemplateCacheFile(uiLayout *layout,
uiItemR(row, &fileptr, "scale", 0, IFACE_("Manual Scale"), ICON_NONE);
}
uiItemR(layout, &fileptr, "velocity_name", 0, NULL, ICON_NONE);
uiItemR(layout, &fileptr, "velocity_unit", 0, NULL, ICON_NONE);
/* TODO: unused for now, so no need to expose. */
#if 0
row = uiLayoutRow(layout, false);

View File

@ -128,6 +128,16 @@ struct CacheReader *CacheReader_open_alembic_object(struct AbcArchiveHandle *han
struct Object *object,
const char *object_path);
bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name);
/* r_vertex_velocities should point to a preallocated array of num_vertices floats */
int ABC_read_velocity_cache(struct CacheReader *reader,
const char *velocity_name,
float time,
float fps,
int num_vertices,
float *r_vertex_velocities);
#ifdef __cplusplus
}
#endif

View File

@ -22,6 +22,7 @@
#include <Alembic/AbcMaterial/IMaterial.h>
#include "abc_axis_conversion.h"
#include "abc_reader_archive.h"
#include "abc_reader_camera.h"
#include "abc_reader_curves.h"
@ -70,7 +71,10 @@
#include "WM_api.h"
#include "WM_types.h"
using Alembic::Abc::IV3fArrayProperty;
using Alembic::Abc::ObjectHeader;
using Alembic::Abc::PropertyHeader;
using Alembic::Abc::V3fArraySamplePtr;
using Alembic::AbcGeom::ICamera;
using Alembic::AbcGeom::ICurves;
using Alembic::AbcGeom::IFaceSet;
@ -79,9 +83,11 @@ using Alembic::AbcGeom::INuPatch;
using Alembic::AbcGeom::IObject;
using Alembic::AbcGeom::IPoints;
using Alembic::AbcGeom::IPolyMesh;
using Alembic::AbcGeom::IPolyMeshSchema;
using Alembic::AbcGeom::ISampleSelector;
using Alembic::AbcGeom::ISubD;
using Alembic::AbcGeom::IXform;
using Alembic::AbcGeom::kWrapExisting;
using Alembic::AbcGeom::MetaData;
using Alembic::AbcMaterial::IMaterial;
@ -859,3 +865,136 @@ CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle,
return reinterpret_cast<CacheReader *>(abc_reader);
}
/* ************************************************************************** */
static const PropertyHeader *get_property_header(const IPolyMeshSchema &schema, const char *name)
{
const PropertyHeader *prop_header = schema.getPropertyHeader(name);
if (prop_header) {
return prop_header;
}
ICompoundProperty prop = schema.getArbGeomParams();
if (!has_property(prop, name)) {
return nullptr;
}
return prop.getPropertyHeader(name);
}
bool ABC_has_vec3_array_property_named(struct CacheReader *reader, const char *name)
{
AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
if (!abc_reader) {
return false;
}
IObject iobject = abc_reader->iobject();
if (!iobject.valid()) {
return false;
}
const ObjectHeader &header = iobject.getHeader();
if (!IPolyMesh::matches(header)) {
return false;
}
IPolyMesh mesh(iobject, kWrapExisting);
IPolyMeshSchema schema = mesh.getSchema();
const PropertyHeader *prop_header = get_property_header(schema, name);
if (!prop_header) {
return false;
}
return IV3fArrayProperty::matches(prop_header->getMetaData());
}
static V3fArraySamplePtr get_velocity_prop(const IPolyMeshSchema &schema,
const ISampleSelector &iss,
const std::string &name)
{
const PropertyHeader *prop_header = schema.getPropertyHeader(name);
if (prop_header) {
const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0);
return velocity_prop.getValue(iss);
}
ICompoundProperty prop = schema.getArbGeomParams();
if (!has_property(prop, name)) {
return V3fArraySamplePtr();
}
const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0);
if (velocity_prop) {
return velocity_prop.getValue(iss);
}
return V3fArraySamplePtr();
}
int ABC_read_velocity_cache(CacheReader *reader,
const char *velocity_name,
const float time,
float velocity_scale,
int num_vertices,
float *r_vertex_velocities)
{
AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
if (!abc_reader) {
return -1;
}
IObject iobject = abc_reader->iobject();
if (!iobject.valid()) {
return -1;
}
const ObjectHeader &header = iobject.getHeader();
if (!IPolyMesh::matches(header)) {
return -1;
}
IPolyMesh mesh(iobject, kWrapExisting);
IPolyMeshSchema schema = mesh.getSchema();
ISampleSelector sample_sel(time);
const IPolyMeshSchema::Sample sample = schema.getValue(sample_sel);
V3fArraySamplePtr velocities = get_velocity_prop(schema, sample_sel, velocity_name);
if (!velocities) {
return -1;
}
float vel[3];
int num_velocity_vectors = static_cast<int>(velocities->size());
if (num_velocity_vectors != num_vertices) {
return -1;
}
for (size_t i = 0; i < velocities->size(); ++i) {
const Imath::V3f &vel_in = (*velocities)[i];
copy_zup_from_yup(vel, vel_in.getValue());
mul_v3_fl(vel, velocity_scale);
copy_v3_v3(r_vertex_velocities + i * 3, vel);
}
return num_vertices;
}

View File

@ -53,6 +53,13 @@ typedef struct AlembicObjectPath {
char path[4096];
} AlembicObjectPath;
/* CacheFile::velocity_unit
* Determines what temporal unit is used to interpret velocity vectors for motion blur effects. */
enum {
CACHEFILE_VELOCITY_UNIT_FRAME,
CACHEFILE_VELOCITY_UNIT_SECOND,
};
typedef struct CacheFile {
ID id;
struct AnimData *adt;
@ -78,7 +85,11 @@ typedef struct CacheFile {
short flag;
short draw_flag; /* UNUSED */
char _pad[4];
char _pad[3];
char velocity_unit;
/* Name of the velocity property in the Alembic file. */
char velocity_name[64];
/* Runtime */
struct AbcArchiveHandle *handle;

View File

@ -2052,6 +2052,10 @@ enum {
MOD_NORMALEDIT_MIX_MUL = 3,
};
typedef struct MeshCacheVertexVelocity {
float vel[3];
} MeshCacheVertexVelocity;
typedef struct MeshSeqCacheModifierData {
ModifierData modifier;
@ -2060,11 +2064,31 @@ typedef struct MeshSeqCacheModifierData {
char object_path[1024];
char read_flag;
char _pad[7];
char _pad[3];
float velocity_scale;
/* Runtime. */
struct CacheReader *reader;
char reader_object_path[1024];
/* Vertex velocities read from the cache. The velocities are not automatically read during
* modifier execution, and therefore have to manually be read when needed. This is only used
* through the RNA for now. */
struct MeshCacheVertexVelocity *vertex_velocities;
/* The number of vertices of the Alembic mesh, set when the modifier is executed. */
int num_vertices;
/* Time (in frames or seconds) between two velocity samples. Automatically computed to
* scale the velocity vectors at render time for generating proper motion blur data. */
float velocity_delta;
/* Caches the scene time (in seconds) used to lookup data in the Alembic archive when the
* modifier was last executed. Used to access Alembic samples through the RNA. */
float last_lookup_time;
int _pad1;
} MeshSeqCacheModifierData;
/* MeshSeqCacheModifierData.read_flag */

View File

@ -174,6 +174,33 @@ static void rna_def_cachefile(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Object Paths", "Paths of the objects inside the Alembic archive");
/* ----------------- Alembic Velocity Attribute ----------------- */
prop = RNA_def_property(srna, "velocity_name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop,
"Velocity Attribute",
"Name of the Alembic attribute used for generating motion blur data");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
static const EnumPropertyItem velocity_unit_items[] = {
{CACHEFILE_VELOCITY_UNIT_SECOND, "SECOND", 0, "Second", ""},
{CACHEFILE_VELOCITY_UNIT_FRAME, "FRAME", 0, "Frame", ""},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "velocity_unit", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "velocity_unit");
RNA_def_property_enum_items(prop, velocity_unit_items);
RNA_def_property_ui_text(
prop,
"Velocity Unit",
"Define how the velocity vectors are interpreted with regard to time, 'frame' means "
"the delta time is 1 frame, 'second' means the delta time is 1 / FPS");
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_define_lib_overridable(false);
rna_def_cachefile_object_paths(brna, prop);

View File

@ -113,7 +113,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
{0, "", 0, N_("Generate"), ""},
{eModifierType_Array,
"ARRAY",
ICON_MOD_ARRAY,
ICON_MOD_ARRAY,
"Array",
"Create copies of the shape with offsets"},
{eModifierType_Bevel,
@ -1699,6 +1699,51 @@ static bool rna_Modifier_show_expanded_get(PointerRNA *ptr)
return md->ui_expand_flag & (1 << 0);
}
static int rna_MeshSequenceCacheModifier_has_velocity_get(PointerRNA *ptr)
{
# ifdef WITH_ALEMBIC
MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data;
return ABC_has_vec3_array_property_named(mcmd->reader, mcmd->cache_file->velocity_name);
# else
return false;
UNUSED_VARS(ptr);
# endif
}
static int rna_MeshSequenceCacheModifier_read_velocity_get(PointerRNA *ptr)
{
# ifdef WITH_ALEMBIC
MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)ptr->data;
if (mcmd->num_vertices == 0) {
return 0;
}
if (mcmd->vertex_velocities) {
MEM_freeN(mcmd->vertex_velocities);
}
mcmd->vertex_velocities = MEM_mallocN(sizeof(MeshCacheVertexVelocity) * mcmd->num_vertices,
"Mesh Cache Velocities");
int num_read = ABC_read_velocity_cache(mcmd->reader,
mcmd->cache_file->velocity_name,
mcmd->last_lookup_time,
mcmd->velocity_scale * mcmd->velocity_delta,
mcmd->num_vertices,
(float *)mcmd->vertex_velocities);
if (num_read == -1 || num_read != mcmd->num_vertices) {
return false;
}
return true;
# else
return false;
UNUSED_VARS(ptr);
# endif
}
#else
/* NOTE: *MUST* return subdivision_type property. */
@ -6066,6 +6111,22 @@ static void rna_def_modifier_meshcache(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_mesh_cache_velocities(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "MeshCacheVertexVelocity", NULL);
RNA_def_struct_ui_text(srna, "Mesh Cache Velocity", "Velocity attribute of an Alembic mesh");
RNA_def_struct_ui_icon(srna, ICON_VERTEXSEL);
prop = RNA_def_property(srna, "velocity", PROP_FLOAT, PROP_VELOCITY);
RNA_def_property_array(prop, 3);
RNA_def_property_float_sdna(prop, NULL, "vel");
RNA_def_property_ui_text(prop, "Velocity", "");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
static void rna_def_modifier_meshseqcache(BlenderRNA *brna)
{
StructRNA *srna;
@ -6108,6 +6169,35 @@ static void rna_def_modifier_meshseqcache(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "velocity_scale", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "velocity_scale");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(
prop,
"Velocity Scale",
"Multiplier used to control the magnitude of the velocity vectors for time effects");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
/* -------------------------- Velocity Vectors -------------------------- */
prop = RNA_def_property(srna, "vertex_velocities", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "vertex_velocities", "num_vertices");
RNA_def_property_struct_type(prop, "MeshCacheVertexVelocity");
RNA_def_property_ui_text(
prop, "Fluid Mesh Vertices", "Vertices of the fluid mesh generated by simulation");
rna_def_mesh_cache_velocities(brna);
prop = RNA_def_property(srna, "has_velocity", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(prop, "Has Velocity Cache", "");
RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_has_velocity_get", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "read_velocity", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(prop, "Read Velocity Cache", "");
RNA_def_property_boolean_funcs(prop, "rna_MeshSequenceCacheModifier_read_velocity_get", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_define_lib_overridable(false);
}

View File

@ -33,6 +33,8 @@
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "MEM_guardedalloc.h"
#include "BKE_cachefile.h"
#include "BKE_context.h"
#include "BKE_lib_query.h"
@ -65,6 +67,9 @@ static void initData(ModifierData *md)
mcmd->cache_file = NULL;
mcmd->object_path[0] = '\0';
mcmd->read_flag = MOD_MESHSEQ_READ_ALL;
mcmd->velocity_scale = 1.0f;
mcmd->vertex_velocities = NULL;
mcmd->num_vertices = 0;
mcmd->reader = NULL;
mcmd->reader_object_path[0] = '\0';
@ -91,6 +96,10 @@ static void freeData(ModifierData *md)
mcmd->reader_object_path[0] = '\0';
BKE_cachefile_reader_free(mcmd->cache_file, &mcmd->reader);
}
if (mcmd->vertex_velocities) {
MEM_freeN(mcmd->vertex_velocities);
}
}
static bool isDisabled(const struct Scene *UNUSED(scene),
@ -154,6 +163,17 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
Mesh *result = ABC_read_mesh(mcmd->reader, ctx->object, mesh, time, &err_str, mcmd->read_flag);
mcmd->velocity_delta = 1.0f;
if (mcmd->cache_file->velocity_unit == CACHEFILE_VELOCITY_UNIT_SECOND) {
mcmd->velocity_delta /= FPS;
}
mcmd->last_lookup_time = time;
if (result != NULL) {
mcmd->num_vertices = result->totvert;
}
if (err_str) {
BKE_modifier_set_error(md, "%s", err_str);
}
@ -221,6 +241,8 @@ static void panel_draw(const bContext *C, Panel *panel)
uiItemR(layout, &ptr, "read_data", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
}
uiItemR(layout, &ptr, "velocity_scale", 0, NULL, ICON_NONE);
modifier_panel_end(layout, &ptr);
}