Page MenuHome

Modifiers: export motion blur velocity through attribute
ClosedPublic

Authored by Brecht Van Lommel (brecht) on Aug 23 2021, 8:40 PM.

Details

Summary

Previously fluid simulation and Alembic modifiers had a dedicated function
to query the velocity for motion blur. Now use a more generic system where
those modifiers output a velocity attribute.

Advantages:

  • Geometry and particle nodes can output velocity through the same mechanism, or read the attribute coming from earlier modifiers.
  • The velocity can be preserved through modifiers like subdivision surface or auto smooth.
  • USD and Alembic previously only output velocity from fluid simulation, now they work with velocity from other sources too.
  • Simplifies the code for renderers like Cycles and exporters like Alembic and USD.

This breaks compatibility:

  • External renderers and exporters accessing these velocities through the Python API now need to use the attribute instead.
  • Existing modifier node setups that create an attribute named "velocity" will render differently with motion blur.

Note USD does not currently support velocity import, likely it's a bit easier
to add on top of this patch.

Diff Detail

Repository
rB Blender

Event Timeline

Brecht Van Lommel (brecht) requested review of this revision.Aug 23 2021, 8:40 PM
Brecht Van Lommel (brecht) created this revision.
Brecht Van Lommel (brecht) planned changes to this revision.Aug 23 2021, 8:52 PM

I still need to change the velocity attribute unit so it's relative to seconds rather than frames, which seems more logical and standard to me. I think the current Alembic export is wrong here and not correctly converting to seconds. Not sure what USD expects, does anyone know?

Great to see this sort of change IMO!

source/blender/blenkernel/intern/attribute.c
226

Not sure your thoughts on this, but this could use the C++ attribute API, like the following:

MeshComponent component;
component.replace(result, GeometryOwnershipType::Editable);
GVArrayPtr attribute = component.attribute_try_get_for_read(velocity_name, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3);
if (!attribute) {
  return;
}
VArray_Span<float3> velocities{attribute->typed<float3>()};

Benefits:

  • Automatic domain interpolation when necessary. (Since this happens automatically in geometry nodes, users likely expect it`
  • Doesn't require adding a new function, uses the same API as geometry nodes
  • "Prettier" casting and data handling.

Nice cleanup and improvement overall. Tested rendering an Alembic file with motion blur and varying topology, this seems to work fine. I just have some minor (inline) comments.

I still need to change the velocity attribute unit so it's relative to seconds rather than frames, which seems more logical and standard to me. I think the current Alembic export is wrong here and not correctly converting to seconds. Not sure what USD expects, does anyone know?

For point clouds, USD seems to use seconds as a unit, as per the documentation (Velocity is measured in position units per second, as per most simulation software.), so I guess it would be similar for other geometry types.

intern/cycles/blender/blender_util.h
607–608

This comment can now be removed.

source/blender/io/alembic/intern/abc_reader_mesh.cc
427

We could avoid some code bloat by taking an ICompoundProperty as input instead of an ISchema (which derives from ICompoundProperty anyway). I tested the following code, call sites don't need to be updated:

static V3fArraySamplePtr get_velocity_prop(const ICompoundProperty &schema,
                                           const ISampleSelector &selector,
                                           const std::string &name)
{
  for (size_t i = 0; i < schema.getNumProperties(); i++) {
    const PropertyHeader &header = schema.getPropertyHeader(i);

    if (header.isCompound()) {
      const ICompoundProperty &prop = ICompoundProperty(schema, header.getName());

      if (has_property(prop, name)) {
        const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0);
        if (velocity_prop) {
          return velocity_prop.getValue(selector);
        }
      }
    }
    else if (header.isArray()) {
      if (header.getName() == name) {
        const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0);
        return velocity_prop.getValue(selector);
      }
    }
  }

  return V3fArraySamplePtr();
}
source/blender/io/usd/intern/usd_writer_mesh.cc
412–413

object is not used anymore, which also gives a compile warning.

Brecht Van Lommel (brecht) marked 4 inline comments as done.
  • Change attribute unit to seconds
  • Address comments
source/blender/blenkernel/intern/attribute.c
226

I think that automatic domain interpolation or datatype conversion in this case is likely to generate bad velocity vectors. I'd rather it only works for float vectors on vertices, better to have no motion blur than broken motion blur.

Kévin Dietrich (kevindietrich) requested changes to this revision.Aug 24 2021, 5:48 PM

It would appear that somehow the math is a bit wrong somewhere, now the motion blur is blowing up (whether I set the unit to frame or second, it should be second since it comes from Houdini):

Test file:

I can look into it a bit later, when I figure out how to make the liquid simulator work.

This revision now requires changes to proceed.Aug 24 2021, 5:48 PM

OK, so I double checked, and I think you just made a slight typo in the Cycles exporter, see inline comment.

Just as a refresher, we have two cases: either the velocity per second (u/s) or per frame (u/f). Since s = 1, u/s = u, but I'll keep the dividend. f is 1 / FPS or s / F. If it is in s/f, we need to multiply by FPS to put it in seconds: u/f * F/s = u*s/F * F/s = u or u/s since s = 1. Inversely, if it is in u/s, we need to divide by FPS to put it in u/f.

If I am not mistaken, we have this table:

Alembic u/su/f
Conversion factor 1.0 FPS
Blender u/su/s
Conversion factor 1.0 / FPS1.0 / FPS
Cycles u/fu/f
intern/cycles/blender/blender_mesh.cpp
1148–1149

This should be scene->motion_shutter_time() / (b_scene.render().fps() / b_scene.render().fps_base()).

b_scene.render().fps() / b_scene.render().fps_base() is basically what's behind the FPS macro in Blender, so we need to divide by it here.

  • Fix wrong motion scale pointed out by Kévin, also needed to do change in fluids for tests to pass.
  • Rebase on master.

Thanks for figuring that out, should be fixed now.

This revision is now accepted and ready to land.Sep 9 2021, 11:29 PM