Parenting Non-Skinned Meshes to glTF Bones

This article explains how to attach rigid, non-skinned geometry directly to skeletal bones within a glTF asset. By utilizing glTF's native scene graph hierarchy, developers can parent standard static meshes—such as weapons, armor pieces, or props—directly to animated bone nodes. This approach eliminates the performance overhead of linear blend skinning, removes the need for vertex weights or inverse bind matrices, and ensures that the attached mesh seamlessly follows skeletal animations across standard runtime engines.

Understanding glTF Nodes and Bones

In the glTF 2.0 specification, a bone is simply an ordinary node in the document's nodes array. While a skin object aggregates these nodes into an indexed joints list for vertex-weighted deformation, the bones themselves remain standard scene graph entities with standard transform properties (translation, rotation, scale, or matrix). Because every bone is a node, any bone can act as a parent to other nodes in the glTF hierarchy.

The Direct Parenting Mechanism

To attach a non-skinned mesh to a bone, link the mesh node as a child of the target bone node without assigning a skin property to the mesh's node.

The structure requires two key configurations:

  1. Target Bone Node: The node acting as the skeleton joint must include the attachment node's index in its children array.
  2. Attachment Mesh Node: A node referencing the mesh index is positioned as a child of the bone. This node defines the local offset (position, rotation, scale) relative to the bone's coordinate space. It must not contain a skin index.

glTF JSON Structure Example

The following excerpt demonstrates a hand bone node (Node 1) parenting a static sword mesh (Node 2):

{
  "nodes": [
    {
      "name": "Armature_Root",
      "children": [1]
    },
    {
      "name": "Bone_Hand_R",
      "translation": [0.0, 1.2, 0.0],
      "rotation": [0.0, 0.0, 0.0, 1.0],
      "children": [2]
    },
    {
      "name": "Prop_Sword",
      "mesh": 0,
      "translation": [0.0, 0.1, 0.05],
      "rotation": [0.7071, 0.0, 0.0, 0.7071]
    }
  ],
  "meshes": [
    {
      "name": "SwordMesh",
      "primitives": [
        {
          "attributes": {
            "POSITION": 0,
            "NORMAL": 1
          },
          "indices": 2
        }
      ]
    }
  ]
}

In this setup:

  • Bone_Hand_R may be referenced in a skin.joints array for deforming a character's body mesh.
  • Prop_Sword is a child of Bone_Hand_R.
  • The SwordMesh primitive only contains basic attributes like POSITION and NORMAL. It does not contain JOINTS_0 or WEIGHTS_0 attributes.

Implementation Considerations

  • Local Coordinate Offsets: The translation, rotation, and scale on the child mesh node act as socket offsets. Modifying these values repositions the object relative to the bone's origin and orientation.
  • Transform Inheritance: When skeletal animations update the transforms of Bone_Hand_R, standard scene graph matrix multiplication causes the child node to follow identically in runtime engines such as Three.js, Babylon.js, and Godot.
  • Exporting from DCC Tools: In digital content creation software like Blender, this is achieved by selecting the prop object, selecting the armature, switching to Pose Mode, selecting the specific target bone, and pressing Ctrl + P to choose Bone parenting. When exporting to glTF 2.0, the exporter translates this relationship into standard nested glTF nodes.