Deforming Geometry (Skinning)

From Derivative
Jump to navigation Jump to search

Overview[edit]

A popular way to deform geometry based on Transforms or a full skeleton is called Skinning. Skinning involves having each point being affected by some number of transforms, each one with a different weight.

Description of Attributes[edit]

Detail attribute pCaptPath[edit]

The pCaptPath detail attribute is an index attribute that contains a list of paths to COMPs are used as bones. The first path is for index 0, the next for index 1 etc. You can use the SOP to DAT to inspect the values of this attribute.

Detail attribute pCaptData[edit]

This attribute has the same number of entries as pCaptPath. It contains 20 floats per entry. The first 16 make up a matrix for the bind/rest pose of the joint. The other 4 are unused in TouchDesigner and are legacy values from Houdini.

Point or Vertex attribute pCapt[][edit]

This attribute contains pairs of information. In each pair the first value is the index number of the transform that affects the point, and the second number is the weight for that particular transform. When deforming, TouchDesigner uses the index from this attribute to find the correct COMP, using pCaptPath. The sum of all the weights for a given points should add up to 1. An index of -1 indicates that there is no transform to apply for that entry. The attribute has a size which will indicate the number of entries it has (pCapt[8] for example). This size includes both the indices and the weights, so if pCapt has a size of 8, it means a maximum of 4 transforms (4 transforms and 4 weights) are applied to each point. The size of pCapt will be big enough to accommodate the point with the most transforms deforming it. It's possible (or even likely) that there will be just one or two points in a geometry that are affected by many more transforms than the rest, you can use the Bone Group SOP to trim out these points and make pCapt smaller.

Unsupported Houdini attributes[edit]

pCaptSkelRoot - This attribute is the path to the root of the skeleton in Houdini. Since it's unlikely the path in Houdini will be a match to the path in TouchDesigner, we don't support this attribute. Instead there is a Skeleton Root Path parameter in our Deform SOP and Deform page of MATs.

pCaptAlpha - This attribute isn't supported.

pCaptFrame - This attribute isn't supported.

Deforming on the CPU[edit]

To deform on the CPU simply use the Deform SOP. The Deform SOP uses Point Capture Attributes rather than Vertex Capture Attributes. You'll need to specify the path to the skeleton in the Skeleton Root Path parameter.

For example, to deform in FBX on the CPU: an Import Select SOP with capture attributes can be used as an input to the Deform SOP. The skeleton root path of the Deform SOP will be the FBX COMP.

Deforming on the GPU[edit]

Deforming on the GPU is very fast (practically free), but requires a bit more work to setup. The first issue is that most GPUs can only handle the required data for between 50 and 200 bones in a single pass. What this means is that the geometry needs to be split up into subsections, each of which are affected by a different subset of the bones of the skeleton. These are called Bone Groups. The Bone Group SOP will split up geometry with capture attributes into different bone groups. The detail attributes pCaptData and pCaptPath will be split up into detail attributes with postfixes starting at 0. So for example if the geometry gets split into two bone groups, there will be detail attributes pCaptData0, pCaptPath0, pCaptData1, and pCaptPath1. The original pCaptData and pCaptPath will be deleted. The point/vertex attribute pCapt will become a vertex attribute named pCapt. There will also be primitives groups named boneGroup0, boneGroup1 that contain all of the primitives in each bone group.

You should isolate out each of the primitive bone groups using the Delete SOP. Next drop down a MAT (like the Phong MAT) that supports deforms. You will need to give the MAT a few pieces of information:

  • The path to the SOP that contains the deform information you want this MAT to use. This is generally the Delete SOP, or some node after it.
  • The name of the pCaptData and pCaptPath attribute. So for example if you are rendering bone group 0 with this MAT, you'd specify pCaptData0 and pCaptPath0.
  • The path to the root of the skeleton.

Deforming using a GLSL MAT[edit]

The GLSL MAT supports automatic deform code, you just setup the options on the deform page just like a Phong MAT, and call [code]TDDeform()[/code] on vertex positions and normals to deform them. The correct deform code is automatically generated and prepended to your shader code when it's sent to the GPU.

Writing your own deform code[edit]

You can write whatever deform code you want yourself in a GLSL shader. Here is how the automatically generated deform code looks in case you want to use this as a starting point. In this example the pCapt attribute has 12 entries (pCapt[12] in the info popup)., which means it has 6 bones it can reference. Since they are collapsed into vec4 for the shader inputs, this means pCapt in the shader is declared as in vec4 pCapt[3], since that is 12 entries total. Be careful if the size of pCapt in the SOP is not evenly divisible by 4. In that case the last pair of value in the last pCapt entry in the GLSL shader would be undefined. For exmaple if pCapt was pCapt[10] in the SOP, then pCapt in the shader would still be declared as in vec4 pCapt[3]. However the pCapt[2].z and pCapt[2].w values are undefined and should not be used. The index will not be equal to -1 necessarily, so that can't be checked against.

The matrix returned by TDBoneMat() is composed of the world transform for that bone for that frame and the bind pose. The bind pose doesn't change and is contained in the pCaptData attribute. The world transform is calculated from the COMP for the bone every frame. The are multipled together like this: WorldTransform*BindPose

 vec4 TDSkinnedDeform(vec4 pos)
 {
   vec4 newp = vec4(0.0, 0.0, 0.0, 1.0);
   int index;
   if (pCapt[0].x >= 0.0)
   {
       index = int(pCapt[0].x);
       newp += (TDBoneMat(index) * pos) * pCapt[0].y;
   }
   else
   {
       return pos;
   }
   if (pCapt[0].z >= 0.0)
   {
       index = int(pCapt[0].z);
       newp += (TDBoneMat(index) * pos) * pCapt[0].w;
   }
   if (pCapt[1].x >= 0.0)
   {
       index = int(pCapt[1].x);
       newp += (TDBoneMat(index) * pos) * pCapt[1].y;
   }
   if (pCapt[1].z >= 0.0)
   {
       index = int(pCapt[1].z);
       newp += (TDBoneMat(index) * pos) * pCapt[1].w;
   }
   if (pCapt[2].x >= 0.0)
   {
       index = int(pCapt[2].x);
       newp += (TDBoneMat(index) * pos) * pCapt[2].y;
   }
   if (pCapt[2].z >= 0.0)
   {
       index = int(pCapt[2].z);
       newp += (TDBoneMat(index) * pos) * pCapt[2].w;
   }
   newp.w = 1.0;
   return newp;
 }