Skip to content

HAnim and glTF skins

Andreas Plesch edited this page Dec 8, 2018 · 28 revisions

glTF skins

https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#skins

All skins are stored in the skins array of the asset. Each skin is defined by the inverseBindMatrices property (which points to an accessor with IBM data), used to bring coordinates being skinned into the same space as each joint; and a joints array property that lists the nodes indices used as joints to animate the skin. The order of joints is defined in the skin.joints array and it must match the order of inverseBindMatrices data. The skeleton property points to the node that is the root of a joints hierarchy.

Implementation Note: The matrix defining how to pose the skin's geometry for use with the joints
("Bind Shape Matrix") should be premultiplied to mesh data or to Inverse Bind Matrices.

Implementation Note: Client implementations should apply only the transform of the skeleton root node
to the skinned mesh while ignoring the transform of the skinned mesh node. In the example below, the
translation of node_0 and the scale of node_1 are applied while the translation of node_3 and rotation
of node_4 are ignored.

Skinned Mesh Attributes

The mesh for a skin is defined with vertex attributes that are used in skinning calculations in the vertex shader. The JOINTS_0 attribute data contains the indices of the joints from corresponding joints array that should affect the vertex. The WEIGHTS_0 attribute data defines the weights indicating how strongly the joint should influence the vertex.

From Overview (rephrased)

The skin refers to the InverseBindMatrices. This is an accessor which contains one matrix per joint node. Each matrix transforms the mesh into the local space of the joint.

The globalJointTransform is the transform of the joint to world space.

The jointMatrix transforms a mesh vertex to world space and is:

jointMatrix[j] = inverse(globalTransform) * globalJointTransform[j] * inverseBindMatrix[j]

The globalTransform is transform surrounding the skinned mesh node needs to be ignored per spec. and so is cancelled out.

skinned mesh node transform discussion

https://github.com/KhronosGroup/glTF/pull/1195

Strangely, the parent transform of skinned mesh nodes are ignored. But, the transform of the 'skeleton' root node is applied. I believe the reason is that the globalJointTransforms already are global.

https://github.com/KhronosGroup/glTF/issues/1270 is about how to ignore the skeleton key.

Example

https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Animation_Skin/Animation_Skin_01.gltf

is a basic example:

{
  "accessors": [
    {
      "bufferView": 0,
      "componentType": 5126,
      "count": 6,
      "type": "VEC3",
      "max": [
        0.25,
        0.0,
        0.2
      ],
      "min": [
        -0.25,
        0.0,
        -0.2
      ],
      "name": "Positions Accessor"
    },
    {
      "bufferView": 1,
      "componentType": 5125,
      "count": 12,
      "type": "SCALAR",
      "name": "Indices Accessor"
    },
    {
      "bufferView": 2,
      "componentType": 5126,
      "count": 6,
      "type": "VEC4",
      "name": "weights accessor"
    },
    {
      "bufferView": 3,
      "componentType": 5123,
      "count": 6,
      "type": "VEC4",
      "name": "joint indices accessor"
    },
    {
      "bufferView": 4,
      "componentType": 5126,
      "count": 2,
      "type": "MAT4",
      "name": "IBM"
    },
    {
      "bufferView": 5,
      "componentType": 5126,
      "count": 3,
      "type": "SCALAR",
      "max": [
        2.0
      ],
      "min": [
        0.0
      ],
      "name": "Animation Sampler Input"
    },
    {
      "bufferView": 6,
      "componentType": 5126,
      "count": 3,
      "type": "VEC4",
      "name": "Animation Sampler Output"
    }
  ],
  "animations": [
    {
      "channels": [
        {
          "sampler": 0,
          "target": {
            "node": 2,
            "path": "rotation"
          }
        }
      ],
      "samplers": [
        {
          "input": 5,
          "output": 6
        }
      ]
    }
  ],
  "asset": {
    "generator": "glTF Asset Generator",
    "version": "2.0"
  },
  "buffers": [
    {
      "uri": "Animation_Skin_01.bin",
      "byteLength": 452
    }
  ],
  "bufferViews": [
    ...
  ],
  "materials": [
    {
      "pbrMetallicRoughness": {
        "baseColorFactor": [
          0.8,
          0.8,
          0.8,
          1.0
        ]
      },
      "doubleSided": true
    }
  ],
  "meshes": [
    {
      "primitives": [
        {
          "attributes": {
            "POSITION": 0,
            "WEIGHTS_0": 2,
            "JOINTS_0": 3
          },
          "indices": 1,
          "material": 0
        }
      ]
    }
  ],
  "nodes": [
    {
      "skin": 0,
      "mesh": 0,
      "name": "plane"
    },
    {
      "children": [
        2
      ],
      "rotation": [
        -0.7071067,
        0.0,
        0.0,
        0.707106769
      ],
      "translation": [
        0.0,
        -0.2,
        0.0
      ],
      "name": "joint0"
    },
    {
      "rotation": [
        -0.258819044,
        0.0,
        0.0,
        0.9659258
      ],
      "translation": [
        0.0,
        0.0,
        0.2
      ],
      "name": "joint1"
    }
  ],
  "scene": 0,
  "scenes": [
    {
      "nodes": [
        0,
        1
      ]
    }
  ],
  "skins": [
    {
      "inverseBindMatrices": 4,
      "joints": [
        1,
        2
      ],
      "name": "skinA"
    }
  ]
}

live:

https://bghgary.github.io/glTF-Assets-Viewer/?manifest=https://raw.githubusercontent.com/KhronosGroup/glTF-Asset-Generator/master/Output/Manifest.json&folder=2&model=1

H-Anim skins

http://www.web3d.org/documents/specifications/19774-1/V2.0/index.html

http://www.web3d.org/documents/specifications/19775-1/V3.3/Part01/components/hanim.html

Simplest skinned example may be Boxman:

http://www.web3d.org/x3d/content/examples/HumanoidAnimation/BoxManIndex.html

Need to have a much simpler example.

Manual translation

plane points:

   -0.25000     0.00000    -0.20000 
    0.25000     0.00000    -0.20000 
   -0.25000     0.00000     0.00000 
    0.25000     0.00000     0.00000 
   -0.25000     0.00000     0.20000 
    0.25000     0.00000     0.20000

plane indices

    0 
    1 
    2 
    2 
    1 
    3 
    2 
    3 
    4 
    4 
    3 
    5 

skin weights (only one joint) per vertex

    1.00000     0.00000     0.00000     0.00000 
    1.00000     0.00000     0.00000     0.00000 
    1.00000     0.00000     0.00000     0.00000 
    1.00000     0.00000     0.00000     0.00000 
    1.00000     0.00000     0.00000     0.00000 
    1.00000     0.00000     0.00000     0.00000 

joint indices, 6, which joint per vertex

0: 0 0 0 0 1: 0 0 0 0 2: 1 0 0 0 3: 1 0 0 0 4: 1 0 0 0 5: 1 0 0 0

inverse bind matrices (per joint), 2

1.00000     0.00000     0.00000     0.00000 
0.00000     1.00000     0.00000     0.00000 
0.00000     0.00000     1.00000     0.00000 
0.00000     0.00000     0.20000     1.00000 

1.00000     0.00000     0.00000     0.00000 
0.00000     1.00000     0.00000     0.00000 
0.00000     0.00000     1.00000     0.00000 
0.00000     0.00000     0.00000     1.00000

animation input

0.00000 
1.00000 
2.00000 

animation output (quaternions)

00.00000     0.00000     0.00000     1.00000 
-0.38268     0.00000     0.00000     0.92388 
00.00000     0.00000     0.00000     1.00000

Putting it together

template

<Scene>
<HAnimHumanoid DEF='gltf_import' info='"authorName=Adam Smith" "authorEmail=aa" "copyright=(C) 2019 Adam Smith - aa" "humanoidVersion=2.0"' name='nonHumanoid' version='2.0'>
  <Coordinate DEF='SKINCOORD' containerField='skinCoord' point='
   -0.25000     0.00000    -0.20000 
    0.25000     0.00000    -0.20000 
   -0.25000     0.00000     0.00000 
    0.25000     0.00000     0.00000 
   -0.25000     0.00000     0.20000 
    0.25000     0.00000     0.20000
'/>
  <HAnimJoint DEF='glTFrootnode' containerField='skeleton' name='HumanoidRoot' skinCoordIndex='' skinCoordWeight=''> 
    <HAnimJoint DEF='node_1' name='firstglTFjoint' skinCoordIndex='' skinCoordWeight=''
     translation='0 -0.2 0' rotation='-1 0 0 1.57'>
      <HAnimJoint DEF='node_1_IBM' translation='0 0 0.2' name='IBMfirstglTFjoint' skinCoordIndex='0 1' skinCoordWeight='1 1'/>
      <HAnimJoint DEF='node_2' name='secondglTFjoint' skinCoordIndex='' skinCoordWeight=''
       translation='0 0 0.2' rotation='-1 0 0 0.5235'>
        <HAnimJoint DEF='node_2_IBM' translation='0 0 0' name='IBMsecondglTFjoint' skinCoordIndex='2 3 4 5' skinCoordWeight='1 1 1 1'/>
      </HAnimJoint>
    </HAnimJoint>
  </HAnimJoint>
  <Group containerField='skin'>
    <Shape DEF='Plane'>
      <Appearance>
        <Material diffuseColor='0 0 1' transparency='0.5'/>
      </Appearance> 
      <IndexedTriangleSet solid='false' index='
    0     1    2 
    2     1    3 
    2     3    4 
    4     3    5 
      '>
        <Coordinate USE='SKINCOORD'/>
      </IndexedTriangleSet>
    </Shape>
  </Group>
  <!-- top-level joint references --> 
  <HAnimJoint USE='glTFrootnode' containerField='joints'/> 
  <HAnimJoint USE='node_1' containerField='joints'/>
  <HAnimJoint USE='node_1_IBM' containerField='joints'/>
  <HAnimJoint USE='node_2' containerField='joints'/>
  <HAnimJoint USE='node_2_IBM' containerField='joints'/>
</HAnimHumanoid>
<TimeSensor DEF='ani0clock' cycleInterval='2' loop='true'/>
<OrientationInterpolator DEF='ani0rotator' key='0 0.5 1' keyValue='-1 0 0 0 -1 0 0 0.7854 -1 0 0 0'/>
<ROUTE fromNode='ani0clock' fromField='fraction_changed' toNode='ani0rotator' toField='set_fraction'/>
<ROUTE fromNode='ani0rotator' fromField='value_changed' toNode='node_2' toField='set_rotation'/>
</Scene>

see http://andreasplesch.github.io/Library/Models/HAnim/glTF/InlineViewer.html

The skin weights in HAnim refer to the index in skincoordindex while the weights in glTF refer to the vertex index directly.

glTF skinned mesh becomes Humanoid skin field value.

glTF joints of skin become HAnim joints of Humanoid with the same hierarchy.

Ignore skeleton key (?).

There may be multiple root joints in glTF. So probably best to have a dummy, null trafo skeleton root joint for Humanoid and bring all glTF joints under it.

Use InverseBindMatrix in Wrapper Joint ?

<HAnimJoint name='gltfNodeJoint_0' no weights and indices>
  <HAnimJoint name='IBM_for_NodeJoint_0' skinweights and coordindices/>
</HAnimJoint>

It looks like these HAnim V2.0 Humanoid fields which have values per joint are equivalent to IBM:

  • jointBindingPositions
  • jointBindingRotations
  • jointBindingScales
  • skinBindingCoords, and
  • skinBindingNormals

http://www.web3d.org/documents/specifications/19774-1/V2.0/HAnim/ObjectInterfaces.html#Humanoid

These would still need to be implemented for x3dom.

Candidates for another manual conversion:

RiggedSimple conversion