Skip to content

Instantly share code, notes, and snippets.

@jedypod
Last active January 19, 2024 16:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jedypod/6302723 to your computer and use it in GitHub Desktop.
Save jedypod/6302723 to your computer and use it in GitHub Desktop.
Distort Tracks Gizmo(Description in Comments, because description does not support markdown?)
set cut_paste_input [stack 0]
push $cut_paste_input
Group {
name DistortTracks
help "<b>Distort Tracks Gizmo</b>\n\n<b>About</b>:\nThis gizmo reformats and/or distorts tracking data based on a uv distortion map input. When you are working with CG elements in your comp that are undistorted and padded resolution, sometimes it is useful to reconcile tracking data from a 3d position through a camera into screen space. This data can then be used to do stuff in 2d: track in lens flares, matchmove roto or splinewarps, etc. The problem is that when this tracking data comes back from our padded undistorted 3d scene into distorted, unpadded resolution comp land, it doesn't line up. \n\n<b>Instructions</b>:\n1. Connect the UV input to a uv distortion map and set the channel that holds it, (for example, a LensDistortion node set to output type Displacement, outputting a UV distortion map into the forward.u and forward.v channels)\n2. Set the padded resolution format and the destination format: Padded resolution is the overscan resolution that you are distorting from, Destination format is the comp resolution you end up in. If they are the same, set them both to be the same.\n3. Add as many tracking points as you want and copy or link the data in. You can show or hide the input and output tracks for convenience. (It is easier to copy the data of many tracks in if you don't see the output track knobs.)\n4. Hit Execute, and all tracks will be distorted. The output tracking data will be copied into each tracks respective trk_out_# knob.\n\n<b>Notes</b>: \nNote that right now this only works with reformat types set to center, no resize, such as you would use when cropping a padded resolution cg plate back to comp resolution before distorting it. Theoretically this gizmo should work to 'reformat' tracking data as well. If you plug in an 'identity' uvmap, the tracking data should be undistorted, but reformatted from the source format to the destination format.\nAlso note that the distorted track output will switch to the reformatted track at the bounds of frame, so that the distorted track does not suddenly pop to 0,0 where the distortion map turns black.\n\nHuge thanks to Ivan Busquets for the ninja-comp technique used to invert the UV Map using a DisplaceGeo.\n\nCreated by Jed Smith. Comments and suggestions welcome: jedypod@gmail.com"
knobChanged "\n# Sets the value of the uv_layer enumeration_knob to the list of input layers, \n# every time the input is changed\nfrom __future__ import with_statement \nn = nuke.thisNode()\nk = nuke.thisKnob()\n\nif k.name() == 'ToggleOutput':\n for knob in n.knobs():\n if 'trk_out_' in knob or 'delete_trk_' in knob:\n if k.value() == True:\n n\[knob].setVisible(True)\n else:\n n\[knob].setVisible(False)\nif k.name() == 'ShowTracks':\n for knob in n.knobs():\n if 'track_' in knob or 'delete_trk_' in knob:\n if k.value() == True:\n n\[knob].setVisible(True)\n else:\n n\[knob].setVisible(False)\n"
tile_color 0xc2bcdcff
addUserKnob {20 DistortTracks}
addUserKnob {3 tnum l INVISIBLE +INVISIBLE}
addUserKnob {41 in l "uv layer" T SelectUV.in}
addUserKnob {6 identity_uv_map l "<b><font size=4>&nbsp;Identity UV Map</b>" t "Enable if you want no transformation. \n\nFor example, if you just want to reformat the tracking data from Padded Format to Destination Format." -STARTLINE}
addUserKnob {41 padded_format l "src format" t "This will be the format that the tracking data was created in. It will be transformed/distorted into the 'Root Format'." T PadFormat.format}
addUserKnob {41 destination_format l "dst format" t "If using a padded format as a source resolution, this will be the destination resolution for the 'distorted' tracking data." T DestinationFormat.format}
addUserKnob {26 spacer l " " T " "}
addUserKnob {6 idistort_map l "Use IDistort Map" t "Distorts tracking data with an iDistort map instead of an STMap compatible UV Map." +STARTLINE}
addUserKnob {6 animated_uvmap l "Animated Distortion Map" t "Check this if the input UVMap is animated. This will slow down analysis." -STARTLINE}
addUserKnob {7 precision l INVISIBLE t "Adjust the subdivide level of the uvmap invert - higher numbers result in increased accuracy at the expense of speed." +INVISIBLE R 2 16}
precision 10.2
addUserKnob {26 ""}
addUserKnob {22 execute l Execute t "Analyze UV Map pixel values at locations specified by tracking data in 'Track', then copy calculated offset track into 'Track Output'." T "from __future__ import with_statement \nfrom __future__ import print_function\n\ndef distort_track_data():\n \"\"\" Code to go in the \"Execute\" button of the DistortTracks node\n \"\"\"\n n = nuke.thisNode()\n padded_format = n\['padded_format'].value()\n destination_format = n\['destination_format'].value()\n # Set Padded and Destination Format Resolutions. You sample a 'format' knob's value() with .width() and .height()\n pad_res = \[int(padded_format.width()), int(padded_format.height())]\n dest_res = \[int(destination_format.width()), int(destination_format.height())]\n # Assume uvmap does not animate, for faster sampling of uvmap colors.\n uvmap_animation = False\n\n\n track_list = \[]\n for knob in n.knobs():\n if 'track_' in knob:\n track_list.append(n\[knob])\n for track in track_list:\n track_number = track.name().split('_')\[-1]\n output = n.knob('trk_out_\{0\}'.format(track_number))\n output.setAnimated()\n channels = \['rgba.red', 'rgba.green']\n\n with n:\n curveTool = nuke.toNode('CurveTool')\n crop_to1px = nuke.toNode('CropToReformatTrack')\n framehold = nuke.toNode('MasterFramehold')\n\n # destination_format = nuke.toNode('DestinationFormat').knob('format').value()\n # padded_format = nuke.toNode('PadFormat1').knob('format').value()\n crop_to1px\['pos'].clearAnimated()\n crop_to1px\['pos'].setAnimated()\n reformatted_track_position = crop_to1px\['pos'].animations()\n\n # Handle case if Track knob has expression instead of keyframes\n # Remove expression and copy each keyframe from source knob to this knob\n if track.hasExpression(0) and track.hasExpression(1):\n orig_exp = track.animation(0).expression().split('.')\n print(\"to - \", orig_exp\[1])\n trk_srcnode = nuke.toNode(orig_exp\[1])\n trk_srcknob = trk_srcnode\[orig_exp\[2]]\n track.clearAnimated()\n track.setAnimated()\n if trk_srcnode.Class() == 'Tracker4':\n # Deal with expression links to Tracker4 nodes\n # http://forums.thefoundry.co.uk/phpBB2/viewtopic.php?t=8130\n # http://forums.thefoundry.co.uk/phpBB2/viewtopic.php?t=8245\n track_index = int(orig_exp\[3])-1\n ch_x = 31*track_index + 2\n ch_y = 31*track_index + 3\n # Use the root framerange for tracker range - not possible to determine animation range\n fr = nuke.root().frameRange()\n for f in range(fr.first(), fr.last()):\n track.animation(0).setKey(f, trk_srcknob.getValueAt(f, ch_x))\n track.animation(1).setKey(f, trk_srcknob.getValueAt(f, ch_y))\n else:\n for index in \[0, 1]:\n track_knob_animcurve = track.animation(index)\n track_knob_animcurve.addKey(trk_srcknob.animation(index).keys())\n\n # Get first and last keyframes on \"track\" animation curve\n track_data = track.animations()\n\n first_keyframe_x = int(track_data\[0].keys()\[0].x)\n last_keyframe_x = int(track_data\[0].keys()\[-1].x+1)\n first_keyframe_y = int(track_data\[1].keys()\[0].x)\n last_keyframe_y = int(track_data\[1].keys()\[-1].x+1)\n\n print(\"Animation range x is\", first_keyframe_x, last_keyframe_x)\n print(\"Animation range y is\", first_keyframe_y, last_keyframe_y)\n\n # Reformat Track Position Data\n # Get difference in source and destination formats. This assumes 'resize type: none, center' reformat, \n # for example, for removing padded pixels before applying lens distortion to cg\n pad_diff = \[(pad_res\[0] - dest_res\[0]) / 2, (pad_res\[1] - dest_res\[1]) / 2]\n print(\"Pad Difference is (\{0\}x\{1\} - \{2\}x\{3\})/2 = \{4\}x\{5\}\".format(padded_format.width(), padded_format.height(), destination_format.width(), destination_format.height(), pad_diff\[0], pad_diff\[1]))\n\n # Find smallest first_keyframe and biggest last_keyframe - in case there are keys in one dimension that are not in the other\n if first_keyframe_x < first_keyframe_y:\n first_keyframe = first_keyframe_x\n else:\n first_keyframe = first_keyframe_y\n if last_keyframe_x > last_keyframe_y:\n last_keyframe = last_keyframe_x\n else:\n last_keyframe = last_keyframe_y\n if uvmap_animation == False:\n framehold.knob('first_frame').setValue(first_keyframe)\n \n \"\"\"\n ################################\n # How STMapping Works\n ################################\n http://forums.thefoundry.co.uk/phpBB2/viewtopic.php?t=2398&sid=a2e5572da996cc31235f4b5d606b8ec5\n http://forums.thefoundry.co.uk/phpBB2/viewtopic.php?p=3015&sid=7705835d3af32b8371ab7f4af5815bc9\n Every pixel of the uvmap represents the source pixel location. For example, a uvmap pixel says \"use the pixel from this location as me!\"\n That location is encoded as floating point values, which represent fractions of width and height. For example, x position 512 in a 1080p frame would be encoded as 1920/512 = .26666667\n On a MergeExpression node, an stmap operation basically is this: for every pixel in B, use the pixel from the coordinates specified by the pixel in A (the UVMAP).\n Br(Ar*width, Ag*height) <- that is an stmap with impulse filtering on the red channel\n\n To invert the stmap, for every pixel you would have to find the SourcePixel, and in that pixel's location, put the current pixelCoordinates.\n Or: in each pixel's location, put the pixelCoordinates of the pixel that references this pixel's location\n To map from Undistorted to Distorted position, you need to lookup the pixel value at the undistorted position in the undistort uvmap, OR lookup the pixel value at the distorted position in the re-distort map. \n The second is not possible without trial and error, because we are trying to find the distorted position.\n\n You can't do this to the distortion map using an expression node, but you are not limited to this. \n \"\"\"\n\n # Create reformatted track data, so we can sample the uv map in the correct place post-reformat\n for i in \[0, 1]:\n for frame in range(first_keyframe, last_keyframe):\n reformatted_track_position\[i].setKey(frame, track_data\[i].evaluate(frame) - pad_diff\[i])\n # Execute the curvetool with the correct framerange and a frame increment of 1,\n # To get UV pixel values at each point of the reformatted tracking data\n nuke.executeMultiple(\[curveTool,], (\[first_keyframe, last_keyframe, 1],))\n uv_data = curveTool.knob('intensitydata').animations()\n \n\n # Loop through all keyframes, get distorted 2d position values using reformatted track positions\n # and UV distortion data from curveTool analysis. Apply this data to track_output knob\n for i in \[0,1]:\n for frame in range(first_keyframe, last_keyframe):\n reformatted_pos = reformatted_track_position\[i].evaluate(frame)\n original_pos = track_data\[i].evaluate(frame)\n #if original_pos <= 0 or original_pos >= dest_res\[i]:\n if uv_data\[i].evaluate(frame) == 0:\n # Use the original reformat track data if the position is outside of the inverted uvmap bounds makes the distorted track at least slight resemble the original outside of the frame bounds instead of being zero\n #print('uv data is zero, using reformatted track!')\n output.setValueAt( reformatted_pos, frame, i )\n else:\n # Use the pixel coordinates that the uvmap points to at the 'reformatted track' pixel location.\n output.setValueAt( uv_data\[i].evaluate(frame) * dest_res\[i], frame, i ) \n print(\"Frame \{0:04d\} - \{1\} -> \{2\} --> \{3\} -- uv vals \{4\}\".format(frame, round(track_data\[i].evaluate(frame), 2), round(reformatted_pos, 2), uv_data\[i].evaluate(frame) * dest_res\[i], uv_data\[i].evaluate(frame)))\n\nif __name__==\"__main__\":\n distort_track_data()" +STARTLINE}
addUserKnob {22 add_track l "Add Track" t "Add another user track to process" -STARTLINE T "# Add Track Data Button\ndef add_track():\n # Note! A triple single-quoted comment breaks the python knob in nuke, causing to output tons of syntax error messages!\n # Adds another track_data knob, and associated track_output knob, along with a button to remove both\n n = nuke.thisNode()\n #n = nuke.selectedNode()\n # If range knob for this range num already exist, increment range number\n tnum_knob = n\[\"tnum\"]\n tnum = int(tnum_knob.getValue())\n if n.knob(\"track_\{0\}\".format(tnum)) != None:\n tnum += 1\n tnum_knob.setValue(tnum)\n delete_track_knob = nuke.PyScript_Knob(\"delete_trk_\{0\}\".format(tnum), \"Delete track_\{0\}\".format(tnum))\n delete_track_knob.setFlag(nuke.STARTLINE)\n n.addKnob(delete_track_knob)\n delete_track_knob.setCommand('n = nuke.thisNode(); n.removeKnob(n\[\"track_\{0\}\"]); n.removeKnob(n\[\"trk_out_\{0\}\"]); n.removeKnob(n\[\"delete_trk_\{0\}\"])'.format(tnum))\n n.addKnob(nuke.XY_Knob(\"track_\{0\}\".format(tnum)))\n n.addKnob(nuke.XY_Knob(\"trk_out_\{0\}\".format(tnum)))\n \nif __name__==\"__main__\":\n add_track()"}
addUserKnob {6 ShowTracks l "Show Tracks" t "Toggle visibility of undistorted Tracking data" +STARTLINE}
ShowTracks true
addUserKnob {6 ToggleOutput l "Show Output" t "Show or Hide Track Output Knobs. This cleans up the UI when trying to copy tracking data into the track knobs." -STARTLINE}
ToggleOutput true
addUserKnob {26 ""}
}
BackdropNode {
inputs 0
name BackdropNode1
tile_color 0x4c4c4c01
label " Invert UV Map"
note_font_size 100
note_font_color 0x1e1e1eff
xpos 155
ypos 422
bdwidth 791
bdheight 644
}
Input {
inputs 0
name UV
label "\[value number]"
xpos 180
ypos -638
}
Dot {
name Dot6
label " Input"
note_font_size 42
note_font_color 0x7f7f7f01
xpos 214
ypos -558
}
Constant {
inputs 0
name Constant2
xpos 400
ypos -441
}
Switch {
inputs 2
which {{"\[exists parent.input0]"}}
name Switch3
xpos 180
ypos -417
}
Shuffle {
blue black
alpha black
name SelectUV
selected true
xpos 180
ypos -298
}
Remove {
operation keep
channels {rgba.red rgba.green -rgba.blue none}
name Remove1
xpos 180
ypos -248
}
set N7c02c340 [stack 0]
Expression {
expr0 (x+0.5)/(width)
expr1 (y+0.5)/(height)
name Expression1
label "UV map"
xpos 290
ypos -254
}
Dot {
name Dot2
note_font_size 42
note_font_color 0x7f7f7f01
xpos 324
ypos -174
}
push $N7c02c340
Switch {
inputs 2
which {{parent.identity_uv_map}}
name Switch2
label "Identity UV Map Pass-Through"
xpos 180
ypos -184
addUserKnob {20 Timing}
addUserKnob {3 range_num +HIDDEN}
range_num 1
addUserKnob {22 add_range l "Add Range" -STARTLINE T "def add_range():\n n = nuke.thisNode()\n #n = nuke.selectedNode()\n whichknob = n.knob('which')\n rnum = n.knob('range_num').value()\n # If range knob for this range num already exist, increment range number\n if n.knob('range_\{0\}'.format(rnum)) != None:\n rnum += 1\n n.knob('range_num').setValue(rnum)\n sfname = 'startframe_\{0\}'.format(rnum)\n efname = 'endframe_\{0\}'.format(rnum)\n n.addKnob(nuke.Text_Knob('range_\{0\}'.format(rnum), 'range_\{0\}'.format(rnum)))\n n.addKnob(nuke.Int_Knob(sfname, 'Start Frame'))\n n.addKnob(nuke.Int_Knob(efname, 'End Frame'))\n n.knob(sfname).setValue(int(nuke.root()\['first_frame'].getValue()))\n n.knob(efname).setValue(int(nuke.root()\['last_frame'].getValue()))\n \n if whichknob.hasExpression():\n whichknob.setExpression(whichknob.animation(0).expression() + \" || (frame >= \{0\} && frame <= \{1\})\".format(sfname, efname))\n else:\n whichknob.setExpression('(frame >= \{0\} && frame <= \{1\})'.format(sfname, efname))\n n.knob('label').setValue('Range \[value \{0\}]-\[value \{1\}]'.format(sfname, efname))\nif __name__==\"__main__\":\n add_range()"}
}
Dot {
name Dot16
note_font_size 42
note_font_color 0x7f7f7f01
xpos 214
ypos -102
}
set N64747690 [stack 0]
Dot {
name Dot14
label " IDistort Style Map Input"
note_font_size 24
note_font_color 0x7f7f7f01
xpos 324
ypos -102
}
Expression {
expr0 (r+x)/width
expr1 (g+y)/height
expr2 0
name IDistort_To_UVMap
xpos 290
ypos -57
}
set Nb13dd310 [stack 0]
push $N64747690
Switch {
inputs 2
which {{parent.idistort_map}}
name Switch1
xpos 180
ypos -57
addUserKnob {20 Timing}
addUserKnob {3 range_num +HIDDEN}
range_num 1
addUserKnob {22 add_range l "Add Range" -STARTLINE T "def add_range():\n n = nuke.thisNode()\n #n = nuke.selectedNode()\n whichknob = n.knob('which')\n rnum = n.knob('range_num').value()\n # If range knob for this range num already exist, increment range number\n if n.knob('range_\{0\}'.format(rnum)) != None:\n rnum += 1\n n.knob('range_num').setValue(rnum)\n sfname = 'startframe_\{0\}'.format(rnum)\n efname = 'endframe_\{0\}'.format(rnum)\n n.addKnob(nuke.Text_Knob('range_\{0\}'.format(rnum), 'range_\{0\}'.format(rnum)))\n n.addKnob(nuke.Int_Knob(sfname, 'Start Frame'))\n n.addKnob(nuke.Int_Knob(efname, 'End Frame'))\n n.knob(sfname).setValue(int(nuke.root()\['first_frame'].getValue()))\n n.knob(efname).setValue(int(nuke.root()\['last_frame'].getValue()))\n \n if whichknob.hasExpression():\n whichknob.setExpression(whichknob.animation(0).expression() + \" || (frame >= \{0\} && frame <= \{1\})\".format(sfname, efname))\n else:\n whichknob.setExpression('(frame >= \{0\} && frame <= \{1\})'.format(sfname, efname))\n n.knob('label').setValue('Range \[value \{0\}]-\[value \{1\}]'.format(sfname, efname))\nif __name__==\"__main__\":\n add_range()"}
}
Dot {
name Dot15
note_font_size 42
note_font_color 0x7f7f7f01
xpos 214
ypos 66
}
set N8175a680 [stack 0]
Dot {
name Dot8
note_font_size 42
note_font_color 0x7f7f7f01
xpos 104
ypos 66
}
Dot {
name Dot1
label " Output UV Map for Preview"
note_font_size 42
note_font_color 0x7f7f7f01
xpos 104
ypos 1362
}
Output {
name Output1
xpos 70
ypos 1455
}
Constant {
inputs 0
channels rgb
format {{{parent.DestinationFormat.format}}}
name Constant1
xpos 400
ypos 759
}
Reformat {
resize none
pbb true
name DestinationFormat
xpos 400
ypos 879
}
set Nb11d3540 [stack 0]
Reformat {
resize none
name PadFormat
xpos 400
ypos 922
}
Camera2 {
inputs 0
translate {0 0 1}
focal 24.576
name Camera1
xpos 740
ypos 858
}
push $N8175a680
Dot {
name Dot9
label " Input Distortion Map"
note_font_size 42
note_font_color 0x7f7f7f01
xpos 434
ypos 66
}
Grade {
add -0.5
black_clamp false
name Grade4
xpos 400
ypos 663
}
Grade {
multiply {1 {height/width} 1 1}
black_clamp false
name Grade5
xpos 400
ypos 687
}
push $N8175a680
Dot {
name Dot12
note_font_size 42
note_font_color 0x7f7f7f01
xpos 214
ypos 258
}
Dot {
name Dot11
label " Identity UV Map"
note_font_size 42
note_font_color 0x7f7f7f01
xpos 654
ypos 258
}
Remove {
operation keep
channels {rgba.red rgba.green -rgba.blue none}
name Remove2
xpos 620
ypos 276
}
Expression {
expr0 (x+0.5)/(width)
expr1 (y+0.5)/(height)
name Expression6
label "UV map"
xpos 620
ypos 323
}
Card2 {
rows {{input.width/divisions}}
columns {{input.height/divisions}}
control_points {3 3 3 6
1 {-0.5 -0.5 0} 0 {0.1666666865 0 0} 0 {0 0 0} 0 {0 0.1666666865 0} 0 {0 0 0} 0 {0 0 0}
1 {0 -0.5 0} 0 {0.1666666716 0 0} 0 {-0.1666666716 0 0} 0 {0 0.1666666865 0} 0 {0 0 0} 0 {0.5 0 0}
1 {0.5 -0.5 0} 0 {0 0 0} 0 {-0.1666666865 0 0} 0 {0 0.1666666865 0} 0 {0 0 0} 0 {1 0 0}
1 {-0.5 0 0} 0 {0.1666666865 0 0} 0 {0 0 0} 0 {0 0.1666666716 0} 0 {0 -0.1666666716 0} 0 {0 0.5 0}
1 {0 0 0} 0 {0.1666666716 0 0} 0 {-0.1666666716 0 0} 0 {0 0.1666666716 0} 0 {0 -0.1666666716 0} 0 {0.5 0.5 0}
1 {0.5 0 0} 0 {0 0 0} 0 {-0.1666666865 0 0} 0 {0 0.1666666716 0} 0 {0 -0.1666666716 0} 0 {1 0.5 0}
1 {-0.5 0.5 0} 0 {0.1666666865 0 0} 0 {0 0 0} 0 {0 0 0} 0 {0 -0.1666666865 0} 0 {0 1 0}
1 {0 0.5 0} 0 {0.1666666716 0 0} 0 {-0.1666666716 0 0} 0 {0 0 0} 0 {0 -0.1666666865 0} 0 {0.5 1 0}
1 {0.5 0.5 0} 0 {0 0 0} 0 {-0.1666666865 0 0} 0 {0 0 0} 0 {0 -0.1666666865 0} 0 {1 1 0} }
name Card2
xpos 620
ypos 567
addUserKnob {20 User}
addUserKnob {3 divisions}
divisions {{rint(parent.precision)}}
}
DisplaceGeo {
inputs 2
source "rgb absolute"
scale 1
name DisplaceGeo2
xpos 620
ypos 687
}
FrameHold {
firstFrame {{parent.MasterFramehold.knob.first_frame}}
name SlaveFramehold1
xpos 620
ypos 754
disable {{parent.MasterFramehold.disable}}
}
push $Nb11d3540
ScanlineRender {
inputs 3
overscan {{(PadFormat.format.width/DestinationFormat.format.width-1)*PadFormat.format.width} {(PadFormat.format.height/DestinationFormat.format.height-1)*PadFormat.format.height}}
motion_vectors_type distance
name ScanlineRender1
xpos 620
ypos 879
}
FrameHold {
firstFrame 358
name MasterFramehold
label "assume non-animated distortion map"
xpos 620
ypos 940
disable {{parent.animated_uvmap}}
}
Crop {
box {{"pos.x - boxsize/2"} {"pos.y - boxsize/2"} {box.x+boxsize} {box.y+boxsize}}
reformat true
crop false
name CropToReformatTrack
xpos 620
ypos 1167
addUserKnob {20 CTRL l Controls}
addUserKnob {7 boxsize R 1 10}
boxsize 1
addUserKnob {12 pos}
}
CurveTool {
avgframes 1
ROI {480 270 1440 810}
autocropdata {480 270 1440 810}
intensitydata {0 0 0 0}
name CurveTool
xpos 620
ypos 1239
}
push $Nb13dd310
Expression {
expr0 (r*width-x)*inv
expr1 (g*height-y)*inv
expr2 0
name UVMapToIDistort
xpos 290
ypos -9
addUserKnob {20 Invert}
addUserKnob {3 inv l invert}
inv 1
}
end_group
@jedypod
Copy link
Author

jedypod commented Aug 26, 2013

Distort Tracks Gizmo

About:

This gizmo reformats and/or distorts tracking data based on a uv distortion map input. When you are working with CG elements in your comp that are undistorted and padded resolution, sometimes it is useful to reconcile tracking data from a 3d position through a camera into screen space. This data can then be used to do stuff in 2d: track in lens flares, matchmove roto or splinewarps, etc. The problem is that when this tracking data comes back from our padded undistorted 3d scene into distorted, unpadded resolution comp land, it doesn't line up.

Instructions:

  1. Connect the UV input to a uv distortion map and set the channel that holds it, (for example, a LensDistortion node set to output type Displacement, outputting a UV distortion map into the forward.u and forward.v channels)
  2. Set the padded resolution format and the destination format: Padded resolution is the overscan resolution that you are distorting from, Destination format is the comp resolution you end up in. If they are the same, set them both to be the same.
  3. Add as many tracking points as you want and copy or link the data in. You can show or hide the input and output tracks for convenience. (It is easier to copy the data of many tracks in if you don't see the output track knobs.)
  4. Hit Execute, and all tracks will be distorted. The output tracking data will be copied into each tracks respective trk_out_# knob.

Notes:

iDistort Input will theoretically let you plug an iDistort map in and have your tracking data distorted by it. UVMap Animation enabled will severely limit the speed at which the uvmap image data can be sampled, but will enable animated distortion maps.
Note that right now this only works with reformat types set to center, no resize, such as you would use when cropping a padded resolution cg plate back to comp resolution before distorting it. Theoretically this gizmo should work to 'reformat' tracking data as well. If you plug in an 'identity' uvmap, the tracking data should be undistorted, but reformatted from the source format to the destination format.
Also note that the distorted track output will switch to the reformatted track at the bounds of frame, so that the distorted track does not suddenly pop to 0,0 where the distortion map turns black.

Huge thanks to Ivan Busquets for the ninja-comp technique used to invert the UV Map using a DisplaceGeo.

@charlesangus
Copy link

I think the expression in Grade5 needs to be height / (width * pixel_aspect) in order to work on anamorphic footage. Cheers - very useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment