Handling velocity blur in Gaffer is something that has come up periodically ... we really should have a better approach by now, but whenever we think about trying to make a built in node to handle it, we bump up against some ugly special cases that make it a bit hard to define exactly how it should work in all cases, in order to be compatible with every situation where it might be needed.
A basic version of a VelocityBlur node is quite easy to hack up though - I've pasted a simple version implemented in OSL at the end of this email - you can paste this into Gaffer, and you'll get a box that implements a basic version of converting an object to use velocity blur. You just need to set "velocityPrimvar" to the name of a velocity primvar on your cache, and then connect a filter that targets the locations that need to be converted to velocity blur.
Caveats with this quickly thrown together node:
* it assumes the renderer is using "centered motion blur", and the shutter angle is less than 360 ( ie shutter length is less than 1 frame ). The default shutter of [-0.25, 0.25] should work fine, but it will get confused if you try with a shutter like [-0.5, 0.5] ( too long ), or [0, 0.5] ( not centered ).
* it's a bit less efficient than a C++ implementation would be
This hasn't been more of a priority because most people who have needed a node like this have either hacked up an OSL version, or have an internal node suited to their specific pipeline ( eg. I know ImageEngine has a node like this ).
It is very useful functionality to have though, even if it doesn't cover every possibility. It really is about time we implemented this as a built-in node ... but for now, hopefully this example box allows you to render your sim:
-Daniel
import Gaffer
import GafferOSL
import GafferScene
import IECore
import imath
Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 1, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 6, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 5, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:patchVersion", 0, persistent=False )
__children = {}
__children["VelocityBlur"] = Gaffer.Box( "VelocityBlur" )
parent.addChild( __children["VelocityBlur"] )
__children["VelocityBlur"].addChild( GafferOSL.OSLObject( "OSLObject1" ) )
__children["VelocityBlur"]["OSLObject1"]["primitiveVariables"].addChild( Gaffer.NameValuePlug( "P", Gaffer.V3fPlug( "value", defaultValue = imath.V3f( 0, 0, 0 ), interpretation = IECore.GeometricData.Interpretation.Point ), True, "primitiveVariable", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) )
__children["VelocityBlur"]["OSLObject1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferScene.FilterPlug( "filter", defaultValue = 0, minValue = 0, maxValue = 7, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferOSL.OSLCode( "OSLCode1" ) )
__children["VelocityBlur"]["OSLCode1"]["parameters"].addChild( Gaffer.V3fPlug( "velocity", defaultValue = imath.V3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, interpretation = IECore.GeometricData.Interpretation.Vector ) )
__children["VelocityBlur"]["OSLCode1"]["parameters"].addChild( Gaffer.FloatPlug( "frameOffset", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"]["OSLCode1"]["parameters"].addChild( Gaffer.FloatPlug( "velocityScale", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"]["OSLCode1"]["out"].addChild( Gaffer.V3fPlug( "outP", direction = Gaffer.Plug.Direction.Out, defaultValue = imath.V3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, interpretation = IECore.GeometricData.Interpretation.Vector ) )
__children["VelocityBlur"]["OSLCode1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferOSL.OSLShader( "InVector" ) )
__children["VelocityBlur"]["InVector"].loadShader( "ObjectProcessing/InVector" )
__children["VelocityBlur"]["InVector"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.Expression( "Expression2" ) )
__children["VelocityBlur"]["Expression2"]["__out"].addChild( Gaffer.FloatPlug( "p0", direction = Gaffer.Plug.Direction.Out, defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"]["Expression2"]["__out"].addChild( Gaffer.FloatPlug( "p1", direction = Gaffer.Plug.Direction.Out, defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"]["Expression2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.TimeWarp( "TimeWarp" ) )
__children["VelocityBlur"]["TimeWarp"].setup( GafferScene.ScenePlug( "in", ) )
__children["VelocityBlur"]["TimeWarp"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferScene.ScenePlug( "in", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferScene.MergeScenes( "MergeScenes" ) )
__children["VelocityBlur"]["MergeScenes"]["in"].resize( 3 )
__children["VelocityBlur"]["MergeScenes"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferScene.ScenePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoxIn( "BoxIn" ) )
__children["VelocityBlur"]["BoxIn"].setup( GafferScene.FilterPlug( "out", defaultValue = 0, minValue = 0, maxValue = 7, ) )
__children["VelocityBlur"]["BoxIn"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoxIn( "BoxIn1" ) )
__children["VelocityBlur"]["BoxIn1"].setup( GafferScene.ScenePlug( "out", ) )
__children["VelocityBlur"]["BoxIn1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoxOut( "BoxOut" ) )
__children["VelocityBlur"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) )
__children["VelocityBlur"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( GafferScene.Isolate( "Isolate" ) )
__children["VelocityBlur"]["Isolate"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoxIn( "BoxIn2" ) )
__children["VelocityBlur"]["BoxIn2"].setup( Gaffer.FloatPlug( "out", defaultValue = 0.0, ) )
__children["VelocityBlur"]["BoxIn2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.FloatPlug( "velocityScale", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.BoxIn( "BoxIn3" ) )
__children["VelocityBlur"]["BoxIn3"].setup( Gaffer.StringPlug( "out", defaultValue = 'velocity', ) )
__children["VelocityBlur"]["BoxIn3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["VelocityBlur"].addChild( Gaffer.StringPlug( "velocityPrimitiveVariable", defaultValue = 'velocity', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"], 'uiEditor:emptySections', IECore.StringVectorData( [ ] ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"], 'uiEditor:emptySectionIndices', IECore.IntVectorData( [ ] ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["user"], 'layout:section', 'User' )
__children["VelocityBlur"]["OSLObject1"]["in"].setInput( __children["VelocityBlur"]["TimeWarp"]["out"] )
__children["VelocityBlur"]["OSLObject1"]["filter"].setInput( __children["VelocityBlur"]["BoxIn"]["out"] )
__children["VelocityBlur"]["OSLObject1"]["primitiveVariables"]["primitiveVariable"]["value"].setInput( __children["VelocityBlur"]["OSLCode1"]["out"]["outP"] )
__children["VelocityBlur"]["OSLObject1"]["__uiPosition"].setValue( imath.V2f( 248.812561, 84.3298798 ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'description', 'The filter used to control which parts of the scene are\nprocessed. A Filter node should be connected here.' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'noduleLayout:section', 'right' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'plugValueWidget:type', 'GafferSceneUI.FilterPlugValueWidget' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'layout:section', 'Filter' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["filter"], 'layout:index', 3 )
__children["VelocityBlur"]["OSLCode1"]["parameters"]["velocity"].setInput( __children["VelocityBlur"]["InVector"]["out"]["value"] )
__children["VelocityBlur"]["OSLCode1"]["parameters"]["frameOffset"].setInput( __children["VelocityBlur"]["Expression2"]["__out"]["p1"] )
__children["VelocityBlur"]["OSLCode1"]["parameters"]["velocityScale"].setInput( __children["VelocityBlur"]["BoxIn2"]["out"] )
__children["VelocityBlur"]["OSLCode1"]["code"].setValue( '\noutP = P + ( velocity * velocityScale *frameOffset );' )
__children["VelocityBlur"]["OSLCode1"]["__uiPosition"].setValue( imath.V2f( 235.012756, 84.170723 ) )
__children["VelocityBlur"]["InVector"]["parameters"]["name"].setInput( __children["VelocityBlur"]["BoxIn3"]["out"] )
__children["VelocityBlur"]["InVector"]["__uiPosition"].setValue( imath.V2f( 222.894104, 85.3707199 ) )
__children["VelocityBlur"]["Expression2"]["__uiPosition"].setValue( imath.V2f( 223.603882, 94.6265717 ) )
__children["VelocityBlur"]["TimeWarp"]["speed"].setValue( 0.0 )
__children["VelocityBlur"]["TimeWarp"]["offset"].setInput( __children["VelocityBlur"]["Expression2"]["__out"]["p0"] )
__children["VelocityBlur"]["TimeWarp"]["__uiPosition"].setValue( imath.V2f( 247.312561, 92.4939423 ) )
__children["VelocityBlur"]["TimeWarp"]["in"].setInput( __children["VelocityBlur"]["BoxIn1"]["out"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["TimeWarp"]["in"], 'noduleLayout:section', 'top' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["TimeWarp"]["out"], 'noduleLayout:section', 'bottom' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["in"], 'noduleLayout:section', 'top' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["in"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["in"], 'layout:index', 0 )
__children["VelocityBlur"]["MergeScenes"]["in"][0].setInput( __children["VelocityBlur"]["BoxIn1"]["out"] )
__children["VelocityBlur"]["MergeScenes"]["in"][1].setInput( __children["VelocityBlur"]["Isolate"]["out"] )
__children["VelocityBlur"]["MergeScenes"]["objectMode"].setValue( 1 )
__children["VelocityBlur"]["MergeScenes"]["__uiPosition"].setValue( imath.V2f( 214.647873, 68.0741196 ) )
__children["VelocityBlur"]["out"].setInput( __children["VelocityBlur"]["BoxOut"]["__out"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["out"], 'description', 'The processed output scene.' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["out"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["out"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["out"], 'layout:index', 1 )
__children["VelocityBlur"]["BoxIn"]["name"].setValue( 'filter' )
__children["VelocityBlur"]["BoxIn"]["__in"].setInput( __children["VelocityBlur"]["filter"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn"]["__in"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn"]["__in"], 'description', 'The filter used to control which parts of the scene are\nprocessed. A Filter node should be connected here.' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn"]["__in"], 'noduleLayout:section', 'right' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn"]["__in"], 'plugValueWidget:type', 'GafferSceneUI.FilterPlugValueWidget' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn"]["out"], 'noduleLayout:section', 'left' )
__children["VelocityBlur"]["BoxIn"]["__uiPosition"].setValue( imath.V2f( 279.794495, 87.9299088 ) )
__children["VelocityBlur"]["BoxIn1"]["__in"].setInput( __children["VelocityBlur"]["in"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn1"]["__in"], 'noduleLayout:section', 'top' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn1"]["out"], 'noduleLayout:section', 'bottom' )
__children["VelocityBlur"]["BoxIn1"]["__uiPosition"].setValue( imath.V2f( 214.647873, 100.825974 ) )
__children["VelocityBlur"]["BoxOut"]["in"].setInput( __children["VelocityBlur"]["MergeScenes"]["out"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxOut"]["__out"], 'description', 'The processed output scene.' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxOut"]["__out"], 'nodule:type', 'GafferUI::StandardNodule' )
__children["VelocityBlur"]["BoxOut"]["passThrough"].setInput( __children["VelocityBlur"]["BoxIn1"]["out"] )
__children["VelocityBlur"]["BoxOut"]["enabled"].setInput( __children["VelocityBlur"]["enabled"] )
__children["VelocityBlur"]["BoxOut"]["__uiPosition"].setValue( imath.V2f( 216.147873, 57.4523201 ) )
__children["VelocityBlur"]["__uiPosition"].setValue( imath.V2f( 226.754227, 80.5332108 ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["__uiPosition"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["enabled"], 'layout:section', 'Node' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["enabled"], 'layout:index', 4 )
__children["VelocityBlur"]["Isolate"]["in"].setInput( __children["VelocityBlur"]["OSLObject1"]["out"] )
__children["VelocityBlur"]["Isolate"]["filter"].setInput( __children["VelocityBlur"]["BoxIn"]["out"] )
__children["VelocityBlur"]["Isolate"]["__uiPosition"].setValue( imath.V2f( 248.812561, 76.1658173 ) )
__children["VelocityBlur"]["BoxIn2"]["name"].setValue( 'velocityScale' )
__children["VelocityBlur"]["BoxIn2"]["__in"].setInput( __children["VelocityBlur"]["velocityScale"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn2"]["__in"], 'noduleLayout:section', 'left' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn2"]["__in"], 'label', 'velocityScale' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn2"]["__in"], 'description', '' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn2"]["__in"], 'noduleLayout:visible', True )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn2"]["out"], 'noduleLayout:section', 'right' )
__children["VelocityBlur"]["BoxIn2"]["__uiPosition"].setValue( imath.V2f( 217.11116, 79.3658905 ) )
__children["VelocityBlur"]["velocityScale"].setValue( 1.0 )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'noduleLayout:section', 'left' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'description', '' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'noduleLayout:visible', True )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'layout:index', 2 )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityScale"], 'nodule:type', '' )
__children["VelocityBlur"]["BoxIn3"]["name"].setValue( 'velocityPrimitiveVariable' )
__children["VelocityBlur"]["BoxIn3"]["__in"].setInput( __children["VelocityBlur"]["velocityPrimitiveVariable"] )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn3"]["__in"], 'noduleLayout:section', 'left' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn3"]["__in"], 'description', '' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn3"]["__in"], 'noduleLayout:visible', True )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["BoxIn3"]["out"], 'noduleLayout:section', 'right' )
__children["VelocityBlur"]["BoxIn3"]["__uiPosition"].setValue( imath.V2f( 207.637039, 85.9714661 ) )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityPrimitiveVariable"], 'noduleLayout:section', 'left' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityPrimitiveVariable"], 'description', '' )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityPrimitiveVariable"], 'noduleLayout:visible', True )
Gaffer.Metadata.registerValue( __children["VelocityBlur"]["velocityPrimitiveVariable"], 'nodule:type', '' )
__children["VelocityBlur"]["Expression2"]["__engine"].setValue( 'OSL' )
__children["VelocityBlur"]["Expression2"]["__expression"].setValue( '\nfloat f = context("frame");\nfloat intF = round( f );\n\nparent.__out.p0 = intF;\nparent.__out.p1 = f - intF;' )
del __children