Skip to content

Commit

Permalink
Add motionblur section.
Browse files Browse the repository at this point in the history
  • Loading branch information
LucaScheller committed Apr 6, 2024
1 parent d8dde97 commit 98d2c3d
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 4 deletions.
122 changes: 120 additions & 2 deletions code/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,124 @@ def Xform "explosion" (
# stage.Export(stage_identifier)
#// ANCHOR_END: animationFPS

#// ANCHOR: animationMotionVelocityAcceleration
import numpy as np

from pxr import Sdf, Usd, UsdGeom


MOTION_ATTRIBUTE_NAMES_BY_TYPE_NAME = {
UsdGeom.Tokens.Mesh: (UsdGeom.Tokens.points, UsdGeom.Tokens.velocities, UsdGeom.Tokens.accelerations),
UsdGeom.Tokens.Points: (UsdGeom.Tokens.points, UsdGeom.Tokens.velocities, UsdGeom.Tokens.accelerations),
UsdGeom.Tokens.BasisCurves: (UsdGeom.Tokens.points, UsdGeom.Tokens.velocities, UsdGeom.Tokens.accelerations),
UsdGeom.Tokens.PointInstancer: (UsdGeom.Tokens.positions, UsdGeom.Tokens.velocities, UsdGeom.Tokens.accelerations)
}
# To lookup schema specific names
# schema_registry = Usd.SchemaRegistry()
# schema = schema_registry.FindConcretePrimDefinition("Mesh")
# print(schema.GetPropertyNames())

def compute_time_derivative(layer, prim_spec, attr_name, ref_attr_name, time_code_inc, multiplier=1.0):
ref_attr_spec = prim_spec.attributes.get(ref_attr_name)
if not ref_attr_spec:
return
attr_spec = prim_spec.attributes.get(attr_name)
if attr_spec:
return
time_codes = layer.ListTimeSamplesForPath(ref_attr_spec.path)
if len(time_codes) == 1:
return
center_time_codes = {idx: t for idx, t in enumerate(time_codes) if int(t) == t}
if not center_time_codes:
return
attr_spec = Sdf.AttributeSpec(prim_spec, attr_name, Sdf.ValueTypeNames.Vector3fArray)
time_code_count = len(time_codes)
for time_code_idx, time_code in center_time_codes.items():
if time_code_idx == 0:
time_code_prev = time_code
time_code_next = time_codes[time_code_idx+1]
elif time_code_idx == time_code_count - 1:
time_code_prev = time_codes[time_code_idx-1]
time_code_next = time_code
else:
time_code_prev = time_codes[time_code_idx-1]
time_code_next = time_codes[time_code_idx+1]
time_interval_scale = 1.0/(time_code_next - time_code_prev)
ref_prev = layer.QueryTimeSample(ref_attr_spec.path, time_code_prev)
ref_next = layer.QueryTimeSample(ref_attr_spec.path, time_code_next)
if not ref_prev or not ref_next:
continue
if len(ref_prev) != len(ref_next):
continue
ref_prev = np.array(ref_prev)
ref_next = np.array(ref_next)
value = ((ref_next - ref_prev) * time_interval_scale) / (time_code_inc * 2.0)
layer.SetTimeSample(attr_spec.path, time_code, value * multiplier)

def compute_velocities(layer, prim_spec, time_code_fps, multiplier=1.0):
# Time Code
time_code_inc = 1.0/time_code_fps
prim_type_name = prim_spec.typeName
if prim_type_name:
# Defined prim type name
attr_type_names = MOTION_ATTRIBUTE_NAMES_BY_TYPE_NAME.get(prim_type_name)
if not attr_type_names:
return
pos_attr_name, vel_attr_name, _ = attr_type_names
else:
# Fallback
pos_attr_name, vel_attr_name, _ = MOTION_ATTRIBUTE_NAMES_BY_TYPE_NAME[UsdGeom.Tokens.Mesh]
pos_attr_spec = prim_spec.attributes.get(pos_attr_name)
if not pos_attr_spec:
return
# Velocities
compute_time_derivative(layer,
prim_spec,
vel_attr_name,
pos_attr_name,
time_code_inc,
multiplier)

def compute_accelerations(layer, prim_spec, time_code_fps, multiplier=1.0):
# Time Code
time_code_inc = 1.0/time_code_fps
prim_type_name = prim_spec.typeName
if prim_type_name:
# Defined prim type name
attr_type_names = MOTION_ATTRIBUTE_NAMES_BY_TYPE_NAME.get(prim_type_name)
if not attr_type_names:
return
_, vel_attr_name, accel_attr_name = attr_type_names
else:
# Fallback
_, vel_attr_name, accel_attr_name = MOTION_ATTRIBUTE_NAMES_BY_TYPE_NAME[UsdGeom.Tokens.Mesh]
vel_attr_spec = prim_spec.attributes.get(vel_attr_name)
if not vel_attr_spec:
return
# Acceleration
compute_time_derivative(layer,
prim_spec,
accel_attr_name,
vel_attr_name,
time_code_inc,
multiplier)

### Run this on a layer with time samples ###
layer = Sdf.Layer.CreateAnonymous()
time_code_fps = layer.timeCodesPerSecond or 24.0
multiplier = 5

def traversal_kernel(path):
if not path.IsPrimPath():
return
prim_spec = layer.GetPrimAtPath(path)
compute_velocities(layer, prim_spec, time_code_fps, multiplier)
compute_accelerations(layer, prim_spec, time_code_fps, multiplier)

with Sdf.ChangeBlock():
layer.Traverse(layer.pseudoRoot.path, traversal_kernel)
#// ANCHOR_END: animationMotionVelocityAcceleration


#// ANCHOR: animationStitchCmdlineTool
...
Expand Down Expand Up @@ -2149,7 +2267,7 @@ def "prim" (

#// ANCHOR: schemasRegistry
from pxr import Plug, Sdf, Tf, Usd
registry = Usd.Schema.Registry()
registry = Usd.SchemaRegistry()

## Get Tf.Type registry entry (which allows us to get the Python class)
## The result can also be used to run IsA checks for typed schemas.
Expand Down Expand Up @@ -2179,7 +2297,7 @@ def "prim" (

#// ANCHOR: schemasRegistryToPrimDefinition
from pxr import Usd
registry = Usd.Schema.Registry()
registry = Usd.SchemaRegistry()
## Useful inspection lookups ##
# Find API schemas. This uses the `Schema Type Name` syntax:
cube_def = registry.FindConcretePrimDefinition("Cube")
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@
[comment]:# - [Volumes ⚒️](./dcc/houdini/fx/volumes.md)
[comment]:# - [Transform Space (Local/World Space) ⚒️](./dcc/houdini/fx/transformspace.md)
- [Frustum Culling](./dcc/houdini/fx/frustumCulling.md)
- [Motion Blur (Deforming/Xforms/Velocity/Acceleration)](./dcc/houdini/fx/motionblur.md)
- [Tips & Tricks](./dcc/houdini/faq/overview.md)
- [Performance Optimizations](./dcc/houdini/performance/overview.md)
26 changes: 24 additions & 2 deletions docs/src/core/elements/animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ Usd encodes time related data in a very simple format:
2. [Layer Offset (A Non-Animateable Time Offset/Scale for Composition Arcs)](#animationLayerOffset)
3. [Reading & Writing default values, time samples and value blocks](#animationReadWrite)
4. [Time Metrics (Frames Per Second & Frame Range)](#animationMetadata)
5. [Stitching/Combining time samples](#animationStitch)
6. [Value Clips (Loading time samples from multiple files)](#animationValueClips)
5. [Motionblur - Computing Velocities and Accelerations](#animationMotionVelocityAcceleration)
6. [Stitching/Combining time samples](#animationStitch)
7. [Value Clips (Loading time samples from multiple files)](#animationValueClips)

## TL;DR - Animation/Time Varying Data In-A-Nutshell <a name="summary"></a>
~~~admonish tip
Expand Down Expand Up @@ -246,6 +247,27 @@ The `startTimeCode` and `endTimeCode` entries give intent hints on what the (use
```
~~~





### Motion Blur - Computing Velocities and Accelerations <a name="animationMotionVelocityAcceleration"></a>
Motion blur is computed by the hydra delegate of your choice using either the interpolated position data or by making use of velocity/acceleration data.
Depending on the image-able schema, the attribute namings slightly differ, e.g. for meshes the names are 'UsdGeom.Tokens.points', 'UsdGeom.Tokens.velocities', 'UsdGeom.Tokens.accelerations'. Check the specific schema for the property names.

~~~admonish warning
Depending on the delegate, you will likely have to set specific primvars that control the sample rate of the position/acceleration data.
~~~

We can also easily derive velocities/accelerations from position data, if our point count doesn't change:
~~~admonish info title=""
```python
{{#include ../../../../code/core/elements.py:animationMotionVelocityAcceleration}}
```
~~~

You can find a interactive Houdini demo of this in our [Houdini - Motion Blur](../../dcc/houdini/fx/motionblur.md) section.

### Stitching/Combining time samples<a name="animationStitch"></a>
When working with Usd in DCCs, we often have a large amount of data that needs to be exported per frame. To speed this up, a common practice is to have a render farm, where multiple machines render out different frame ranges of scene. The result then needs to be combined into a single file or loaded via value clips for heavy data (as described in the next section below).

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions docs/src/dcc/houdini/fx/motionblur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Motion Blur
Motion blur is computed by the hydra delegate of your choice using either the interpolated position data(deformation/xforms) or by making use of velocity/acceleration data.

You can find all the .hip files of our shown examples in our [USD Survival Guide - GitHub Repo](https://github.com/LucaScheller/VFX-UsdSurvivalGuide/tree/main/files/dcc/houdini).

As noted in our [Motion Blur - Computing Velocities and Accelerations](../../../core/elements/animation.md#animationMotionVelocityAcceleration),
we can also easily derive the velocity and acceleration data from our position data, if the point count doesn't change.

![Houdini Motion Data Compute](media/motionblurDeformingVelocityAcceleration.png)

~~~admonish warning
Depending on the delegate, you will likely have to set specific primvars that control the sample rate of the position/acceleration data.
~~~

We can also easily derive velocities/accelerations from position data, if our point count doesn't change:
~~~admonish tip title="Motionblur | Compute | Velocity/Acceleration | Click to expand" collapsible=true
```python
{{#include ../../../../../code/core/elements.py:animationMotionVelocityAcceleration}}
```
~~~
Binary file added files/dcc/houdini/motionblur/MotionBlur.hipnc
Binary file not shown.

0 comments on commit 98d2c3d

Please sign in to comment.