In the rigging category i will talk about character setup , and body deformation.
Rigging a 3d character is to turn a sculpture into an animatable puppet, a lot of concept is shared with stop motion puppet animation:
A skeletal structure is created and serves two function: motion and deformation.
By default maya gives a fair amount of tools to tackle the creation of a skin behavior: cluster, lattice, wrap and wire deformer, blendshape and skinCluster.
Each one can be combined and layered to achieve the desired effect.
Nowadays, the most common workflow in television and featured film animation( when time and budget allows it ) is to design a first pass for the body deformation with a skinCluster and to enhanced it with a pose space deformation system.
The concept is to drive the effect of a set of corrective shapes when a joint reach a pose:
- In 3dStudioMax pose space deformation is possible through the use of the skinmorph modifier.
- In maya we can use Michael Comet’s open source pose space deformer.
Most of the time these two tools works as expected. The correctives shape extraction works flawlessly, but the weight interpolation and mixing tends to be bit unreliable for the most complex case.
The second consideration is the deformation order: the deformer sits on top of the deformation stack in max and after the skinCluster Node in the deformation history in maya, this is not a bad design choice
,just a personal taste.
For this reasons I decided to write a script to extract the corrective shapes, theses shapes will then be used in a regular blendshape Node before a skinCluster.
There is a wealth of information on the skincluster algorithm : its similar to the linear blend skinning or linear vector blending:
A vertex is transformed by N number of joints , each influences have a weight value that controls how rigidly that vertex is attached to a bone. Most of the time to have predictable result the sum of all weight of a vertex is no more than 1.0.
In a more practical standpoint the algorithm attach rigidly the vertex to each influencing bones and the final position is the weighted sum of all the resulting vector.
transformStorage is a vectorArray transformStorage.clear() for each influencing bones: transformStorage.append( ( world space vertex position * currentJointBindPose* currentJointTransformation * currentJointweight ) -world space vertex position ) finalvector = transformStorage if transformStorage.length() > 1: for j in range(1 , transformStorage.length()): finalvector = finalvector + transformStorage[j] finalVertexPosition = world space vertex position + finalvector
In the pseudo code above we assume that it is a good practice and a prerequisite to freeze a mesh transformation and all its parent hierarchy the result is that vertex position in object space is the same as in world space.
currentJointBindPose: This matrix will transform our current vertex from world to object space, attaching this point to the joint .
This value can be retrieve in the skinCluster node in the multi-attribute bindPrematrix: at bindPose maya store for each influence the inverse parent matrix of the related transform node. One interesting point is that this array is connectable: we can adjust the binding position of a joint without loosing the skinning configuration by plugging its inverse parent matrix in the right slot
currentJointTransformation :we operate in world space, the current Joint world space matrix is plugged in the matrix array attribute of the skinCluster
A point multiplied by a matrix transform it into this matrix space .To retrieve a vector we substract the original vertex point from the transformed vertex point.
currentJointweight :can be found in the compound array attribute weightList
Maya implementation for the current influences weight is quite interesting : we have an array of sparse array where we can found the weight of all the joint that influence this vertex. The array indices is used to determine what matrix attribute to use for the calculation
In mel or python we can retrieve these value with the following command:
skinClNode = 'skinCluster' vertId = 15 # index of the current vertex vertexBonesID = cmds.getAttr( skinClNode + '.weightList[%s].weights' % vertId, mi=True)
the -mi flag of the getAttr command return a list of indices that is used in an array attribute , in the maya api we can call Maya.OpenMaya.MFnSkinCluster.getWeights ( path, components, influenceIndices, weights ) to retrieve in one step the weight and indices for a list of component.
This compound array is connectable and we can plug a whole array in it to store, export skinning informations without extracting any single value.
Another creative use of this properties is that we can animate each influences weight value for a vertex component or better create and assign skinning information to topology variable polygonal mesh.
After studying the skincluster algorithm the next step was to define a set of rules to extract our corrective shape.To illustrate the process i will use one character of an old TV commercials we did at hanuman studio for a bowling playhouse.His name is Zazar and they will be with his counterpart Leon the main characters of an animated TV series.
- As design choice my first rule was to restrict the usage of my script to polygonal object that are deformed by a skincluster.
- The second restriction was to limit the type of influences in the skincluster to regular transform nodes like joint, locator or group.
- All deforming mesh transformation are frozen , their pivot placed at the origin and parent under a group that do not inherits transform.
- The last rule was to impose to place the blendshape Node just before the skinCluster
Like any pose space deformer system , the user deals with a two step process:
- rotate and/or translate one or several joints present in the skinCluster to pose a part of the body.
- sculpt an independent copy of this mesh to define the correct look of the skin
Once the target sculpture satisfy the user needs we can start the calculation loop.For optimization purpose each vertex in the skinned mesh(1) is checked against the corresponding vertex ID in the target mesh(2) , and if the the distance between them is greater than a minimal value we can reverse the target mesh tweaking back to the skinned mesh object space(3).
#In order to reverse the sculpted independent shape back to the original skinned mesh object space we must concatenate #the matrix of the influences joint and transform by its inverse the current vertex position of the sculpted mesh: matrixStorage is a MatrixArray matrixStorage.clear() for each influencing bones: matrixStorage.append( currentJointBindPose* currentJointTransformation * currentJointweight ) finalMatrix = matrixStorage if finalMatrix.length() > 1: for j in range(1 , finalMatrix.length()): finalMatrix = finalMatrix + matrixStorage[j] reverseMatrixforcurrentVertex = finalMatrix.inverse() finalVertexPosition = world space vertex position * reverseMatrixforcurrentVertex
One interesting thing i learned when the first working draft for this script was completed was that to extract and mix several corrective shapes one extra step was necessary: negate the contribution of the blendshape node from the extracted corrective morph.
Below we can see a UI to create and manage corrective blendshapes