Saturday, March 05, 2016

Ranger Rig Production Part1

Every year or so I like to take on a big personal rigging project so I can pick up some new tricks without the pressures and constraints of an actual production. This time around I will be mixing in several ventures all aimed at creating a film quality rig that can be distributed to the animation community at large. I will start off by outlining the goals of this project while giving a few details on how I plan on achieving these goals. Now before I get too deep into this, let me issue a fair warning that I have a son on the way any day now, so this project could go several weeks without anything to report. Alrighty then, with my excuses out of the way, let's push on.


  • Work with a small team to develop some rigging tools.  Except for the artsy stuff,  I hope to generate every part of this rig procedurally.  Details on this small team and that development process will be coming soon.
  • Develop a robust facial rigging solution that looks great without a bunch of fancy deformers,  but is able to layer in deformers and blendshapes to push the look to the next level.
  • Push myself to push my skills at cinematic quality rigging.
  • Teach all of you about my process.  Hopefully we will both learn something new!  I won't be going full tutorial style on everything I do here, however I may make tutorials available in one form or another later on.  For now you can check out the Rigging Dojo, where you can get my Python class along with a ton of information on everything rigging related.

If I'm going to rig a character, I suppose I will need to start with a model.  No, not just any model.  It needs to be a great model.  Fortunately for me, the extremely talented Daniel Williams has offered up one of his creations for this mad science project.  I highly encourage you to check out his work.  This is typically the section where I would talk about good topology for rigging, but Danny's models already have fantastic topology.  I will talk briefly about what makes topology "fantastic", and I will do some comparisons between what I look for in a cinematic mesh versus a game ready mesh in later posts.  Before we get into all of that I want to show you what we'll be working with.


Check out this link to see the Ranger in all his rendered glory.

Here are some reasons why I think this topology is Fantastic!

  • The continuous line across the brow.
  • The edge that flows from the nose to the mouth furrow line.
  • The clean circular edges around the mouth.
  • The extruded face between the brows for a nice brow furrow.

While this is a great model, I will suggest the following edits to the face topology.

That's all I have time for tonight.  In my next post I want to start designing the requirements for this rig so I can break the work into chunks.  We will talk about how I determine those requirements, and my process for plotting out how to meet them.  Thanks for stopping by and I hope to see you here next time.

Thursday, March 03, 2016

Surface Based Facial Rigging POC

I pulled out this face rig proof of concept that illustrates the idea of controlling interpolation through the use of surfaces.  All of the animation is driven by just 3 controls, with a set of minor controls that follow along with the majors so you can fine tune.

SurfaceFacePOC_01 from ryan griffin on Vimeo.

Tuesday, March 01, 2016

Leg Rigging in Maya

I wrote up this little leg rigging tutorial with the Python code I captured while manually rigging.  I thought this might be a handy resource for aspiring riggers.  This is basically the process I use when figuring out some new rigging technique.  Later on I will go back and refine the code to make it more concice and versatile.

IK Stretchy no Flip Leg Tutorial

In this tutorial I will walk you through the creation of a robust ik leg setup.  This is not the only way to build a leg, but I feel this setup introduces some pretty useful concepts that can be applied to your own rig.  Lets get started.

Step1:  Create the joints
First we need to draw our joint chain.  In my example I have named the joints ( jnt_pelvis, ikj_hip, ikj_knee, ikj_ankle, ikj_ball, and ikj_toe).  Once the joints are created, you should orient them using Skeleton/Orient Joint.  I chose to use X down the bone and Y up.  This orientation will be important later on, so just make a mental note if you decide to use a different orientation.

Step2: Drawing the IK:
Draw an ikRP solver from ikj_hip to ikj_ankle.
Draw an ikSC solver from ikj_ankle to ikj_ball.
Draw an ikSC solver from ikj_ball to ikj_toe.
Here is an example of how we could do this with Python.
cmds.ikHandle(n= "ikh_leg", sj= "ikj_hip", ee= "ikj_ankle", sol = "ikRPsolver")
cmds.ikHandle(n= "ikh_ball", sj= "ikj_ankle", ee= "ikj_ball", sol = "ikSCsolver")
cmds.ikHandle(n= "ikh_toe", sj= "ikj_ball", ee= "ikj_toe", sol = "ikSCsolver")

Step3:  Grouping the IK:
We need to create groups to use for rotating the toe, foot roll, and so on.  The setup I am presenting is a bit different than most tutorials I have seen, and we need to create some extra groups to accommodate that fact.
Create the following groups.  I will write this in Python format.

footGroups = ("grp_footPivot", "grp_heel", "grp_toe", "grp_ball", "grp_flap")
for item in footGroups:, empty=True, world=True)

grp_footPivot should be left at the origin.
grp_heel should be moved to the heel of the foot geometry.
grp_toe should be moved to ikj_toe.
grp_ball should be moved to ikj_ball.
grp_flap should be moved to ikj_ball
You can move these by first getting the position of the joints using cmds.xform
You can then place the groups with xform again.
hipPos = cmds.xform("ikj_hip", q=True, ws=True, t=True)
anklePos = cmds.xform("ikj_ankle", q=True, ws=True, t=True)
ballPos = cmds.xform("ikj_ball", q=True, ws=True, t=True)
toePos = cmds.xform("ikj_toe", q=True, ws=True, t=True)
cmds.xform("grp_toe", ws=True, t=toePos)
cmds.xform("grp_ball", ws=True, t=ballPos)
cmds.xform("grp_flap", ws=True, t=ballPos)
The heel is tricky. We need an object to set the position

Now we need to put our groups and ik handles into a hierarchy.  

cmds.parent('grp_heel', 'grp_footPivot')
cmds.parent('grp_toe', 'grp_heel')
cmds.parent('grp_ball', 'grp_toe')
cmds.parent('grp_flap', 'grp_toe')
cmds.parent('ikh_leg', 'grp_ball')
cmds.parent('ikh_ball', 'grp_ball')
cmds.parent('ikh_toe', 'grp_flap')
cmds.parent(‘grp_footPivot’, ‘ctrl_leg’)

Step 4:  Foot Control
Create a control object to control your foot.  Snap the controls pivot to your ikj_ankle and be sure to freeze transforms.  I named my foot control “ctrl_leg”.  Now you can parent the “grp_footPivot” to “ctrl_leg”

Step 5:  No Flip Knee
Create a locator. Name it “lctrPv_leg”, and snap it to the position of “jnt_pelvis”. Parent the locator under “jnt_pelvis”.  
pelvisPos = cmds.xform('jnt_pelvis', q=True, ws=True, t=True)
cmds.xform('lctrPv_leg', ws=True, t=pelvisPos)

Select the locator, then the “ikh_leg”, and execute Constrain > Pole Vector.
cmds.poleVectorConstraint ('lctrPv_leg', 'ikh_leg', weight=1)
Create a float attribute called "Twist" on the ikFootCtrl controller.  Select the ctrl_leg and execute this command.
cmds.addAttr( shortName='Twist', longName='Twist', defaultValue=0, k=True)

Create a plusMinusAverage utility, and call it pmaNode_LegTwist.
Create a multiplyDivide utility and call it mdNode_LegTwist.
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_LegTwist')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_LegTwist')

Set up the connections using the following commands :
cmds.connectAttr('ctrl_leg.Twist', 'mdNode_LegTwist.input1X')
cmds.connectAttr('ctrl_leg.ry', 'mdNode_LegTwist.input1Y')
cmds.connectAttr('jnt_pelvis.ry', 'mdNode_LegTwist.input1Z')
cmds.setAttr('mdNode_LegTwist.input2X', -1)
cmds.setAttr('mdNode_LegTwist.input2Y', -1)
cmds.setAttr('mdNode_LegTwist.input2Z', -1)
cmds.connectAttr('mdNode_LegTwist.input1X', 'pmaNode_LegTwist.input1D[0]')
cmds.connectAttr('mdNode_LegTwist.input1Y', 'pmaNode_LegTwist.input1D[1]')
cmds.connectAttr('pmaNode_LegTwist.output1D', 'ikh_leg.twist')

Step 6:  Create the Stretchy IK
Start by creating all of the nodes we will need for the stretch.
cmds.shadingNode("addDoubleLinear", asUtility=True, n='adlNode_LegStretch')
cmds.shadingNode("clamp", asUtility=True, n='clampNode_LegStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_LegStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_KneeStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_AnkleStretch')

Add a “Stretch” attribute to ctrl_leg.‘ctrl_leg’)
cmds.addAttr( shortName='Stretch', longName='Stretch', defaultValue=0, k=True)

Create a distance tool to measure the distance between our hip and ankle joints.
Use Create/Measure Tools/Distance Tool.
Snap one locator to ikj_hip and name it ‘lctrDis_hip’.  Parent this locator to ‘jnt_pelvis’
Snap the other locator to ikj_ankle and name it ‘lctrDis_ankle’.  Parent this locator to grp_heel’
hipPos = cmds.xform('ikj_hip', q=True, ws=True, t=True)
anklePos = cmds.xform('ikj_ankle', q=True, ws=True, t=True)
disDim = cmds.distanceDimension(sp=(hipPos), ep=(anklePos))

cmds.rename('distanceDimension1', 'disDimNode_legStretch')
cmds.rename('locator1', 'lctrDis_hip')
cmds.rename('locator2', 'lctrDis_ankle')
cmds.parent('lctrDis_hip', 'jnt_pelvis')
cmds.parent('lctrDis_ankle', 'grp_ball')

Next we will need to figure out the length of the leg when it is fully extended.  We could do this by moving the leg control until the leg is straight, and querying the distance tools distance attribute, but this is inaccurate and can not be scripted.  Instead we will use python to get the translateX values of our joints.  We will then add those values.

kneeLen = cmds.getAttr('ikj_knee.tx')
print kneeLen
ankleLen = cmds.getAttr('ikj_ankle.tx')
print ankleLen
legLen = (kneeLen + ankleLen)
print legLen

Enter our new found length values into the corresponding Nodes.
cmds.setAttr('adlNode_LegStretch.input2', legLen)
cmds.setAttr('mdNode_LegStretch.input2X', legLen)
cmds.setAttr('mdNode_KneeStretch.input2X', kneeLen)
cmds.setAttr('mdNode_AnkleStretch.input2X', ankleLen)

Connect the nodes to get the final stretch value that will be applied to our joints.

The clamp node lets us control the amount of stretch.
cmds.connectAttr('ctrl_leg.Stretch', 'adlNode_LegStretch.input1')
cmds.setAttr ("clampNode_LegStretch.minR", 12.800084)
cmds.setAttr ("mdNode_LegStretch.operation",  2)

Connect the distance dimension so we always know the current length of the leg.
cmds.connectAttr('disDimNode_legStretch.distance', 'clampNode_LegStretch.inputR')
cmds.connectAttr( 'adlNode_LegStretch.output', 'clampNode_LegStretch.maxR')

Now we feed the total value into a multiply divide so we can distribute the value to our joints.
cmds.connectAttr('clampNode_LegStretch.outputR', 'mdNode_LegStretch.input1X')
cmds.connectAttr('mdNode_LegStretch.outputX', 'mdNode_KneeStretch.input1X')
cmds.connectAttr('mdNode_LegStretch.outputX', 'mdNode_AnkleStretch.input1X')

Finally, we output our new values into the translateX of the knee and ankle joints.
cmds.connectAttr('mdNode_KneeStretch.outputX', 'ikj_knee.tx')
cmds.connectAttr('mdNode_AnkleStretch.outputX', 'ikj_ankle.tx')

Step 7:  Create the Foot Roll
By now you are hopefully familiar with what the lines of code are doing.  For this next part I will not offer too much in depth explanation.  We will create a foot roll attribute that goes all the way from the ball to the toe roll.  We will do this using only nodes.  It may be easier to accomplish this with set driven keys, but I prefer not to use SDKs when possible.

Create Attributes on the ctrl_foot'ctrl_leg')
cmds.addAttr( shortName='Roll_Break', longName='Roll_Break', defaultValue=0, k=True)
cmds.addAttr( shortName='Foot_Roll', longName='Foot_Roll', defaultValue=0, k=True)
Setup the foot roll
Create utility nodes
cmds.shadingNode("condition", asUtility=True, n='conNode_ballRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_negBallRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_toeRoll')
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_ballRoll')
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_toeRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_heelRoll')
cmds.setAttr('pmaNode_toeRoll.operation', 2)
cmds.setAttr ("conNode_toeRoll.operation", 2)
cmds.setAttr ("conNode_toeRoll.colorIfFalseR", 0)
cmds.setAttr ("conNode_toeRoll.colorIfFalseG", 0)
cmds.setAttr ("conNode_toeRoll.colorIfFalseB", 0)
cmds.setAttr ('conNode_heelRoll.operation', 4)
cmds.setAttr('conNode_heelRoll.colorIfFalseB', 0)
cmds.setAttr('conNode_heelRoll.colorIfFalseR', 0)
cmds.setAttr('conNode_heelRoll.colorIfFalseG', 0)
cmds.setAttr("pmaNode_ballRoll.operation", 2)
cmds.setAttr ("conNode_negBallRoll.operation", 3)
cmds.setAttr ("conNode_ballRoll.operation", 3)

Setup Toe
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_toeRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_toeRoll.colorIfTrueR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_toeRoll.secondTerm')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_toeRoll.colorIfFalseR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'pmaNode_toeRoll.input1D[1]')
cmds.connectAttr('conNode_toeRoll.outColorR', 'pmaNode_toeRoll.input1D[0]')
cmds.connectAttr('pmaNode_toeRoll.output1D', 'grp_toe.rx')

Setup Heel
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_heelRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_heelRoll.colorIfTrueR')
cmds.connectAttr('conNode_heelRoll.outColorR', 'grp_heel.rotateX')
Setup Ball
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_ballRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_ballRoll.colorIfTrueR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_negBallRoll.secondTerm')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_negBallRoll.colorIfTrueR')
cmds.connectAttr('conNode_negBallRoll.outColorR', 'pmaNode_ballRoll.input1D[0]')
cmds.connectAttr('grp_toe.rx', 'pmaNode_ballRoll.input1D[1]')
cmds.connectAttr('pmaNode_ballRoll.output1D', 'grp_ball.rx')
cmds.connectAttr('conNode_ballRoll.outColorR', 'conNode_negBallRoll.firstTerm')
cmds.connectAttr('conNode_ballRoll.outColorR', 'conNode_negBallRoll.colorIfFalseR')

Make a Toe Flap
We will need to make a new attribute called Toe_Flap on ctrl_foot.  Then we can connect Toe_Flap to grp_flap rotateX.'ctrl_leg')
cmds.addAttr( shortName='Toe_Flap', longName='Toe_Flap', defaultValue=0, k=True)
cmds.connectAttr('ctrl_leg.Toe_Flap', 'grp_flap.rx')

Step 8:  Pivot for Bank and Twist
Create a new control object.  I am using something that looks like a pin.  Name the control ‘ctrl_footPivot’
Move the control to the grp_ball.
ballPos = cmds.xform('grp_ball', q=True, t=True, ws=True)
cmds.xform('ctrl_footPivot', t=ballPos)

Deselect the ctrl_footPivot, and create an empty group at the origin.  Name the group ‘grp_ctrl_footPivot’.'grp_ctrl_footPivot', empty=True)
Parent the grp_ctrl_footPivot  to  ctrl_footPivot.
cmds.parent('grp_ctrl_footPivot', 'ctrl_footPivot')
Parent the ctrl_footPivot to ctrl_foot and freeze transforms on ctrl_footPivot.
cmds.parent('ctrl_footPivot', ‘ctrl_foot’)
cmds.makeIdentity( apply=True )

Now we will connect the grp_ctrl_footPivot.translate to grp_footPivot.rotatePivot
cmds.connectAttr('grp_ctrl_footPivot.translate', 'grp_footPivot.rotatePivot')
Move grp_ctrl_footPivot to the position of grp_ball.
cmds.xform('grp_ctrl_footPivot', t=ballPos)

Make a couple more attributes for twist and bank, then hook those up to the grp_footPivot.'ctrl_leg')
cmds.addAttr( shortName='Foot_Pivot', longName='Foot_Pivot', defaultValue=0, k=True)
cmds.addAttr( shortName='Foot_Bank', longName='Foot_Bank', defaultValue=0, k=True)
cmds.connectAttr('ctrl_leg.Foot_Pivot', 'grp_footPivot.ry')
cmds.connectAttr('ctrl_leg.Foot_Bank', 'grp_footPivot.rz')

Please email me with any questions or errors.  I do not claim that this is the perfect leg rig.  I designed this to try some new ideas and to incorporate some time tested methods.
-Ryan Griffin

Wednesday, February 17, 2016

Trying to get a handle on ZBrush

This is a WIP that I've been working on for ever.  Generally I work on modeling so I can get a better idea of what makes for good topology and to get a better feel for the process so I can communicate better with the artists. Unfortunately as a rigger I'm a bit of a topology nut so I ended up working backwards from topo to sculpt.  At this point I'm just trying to let topology go for the moment so I can focus on form.