Skip to content

Understanding Vertex Positioning in glTF Models: How Skinning and Node Transformations Affect Final Placement

In the realm of 3D graphics, accurately determining the final position of vertices within a scene is crucial for rendering models correctly. This article delves into how vertex positions are calculated in glTF (GL Transmission Format) models, particularly focusing on scenarios where vertices are either influenced by joints (through skinning) or not.

Scenario 1: No Joint Influence

When a vertex is not influenced by any joints (i.e., no skinning is applied), the process to determine its final position must still account for node transformations. These transformations include translation, rotation, and scaling, which collectively modify the vertex's position from local to global coordinates.

Steps to Calculate Vertex Position:

  1. Retrieve Local Vertex Position: The vertex’s initial coordinates are taken from the POSITION attribute, which defines its position relative to the node.
  2. Apply Node Transformations:
    • Scale: Adjust the vertex position according to the node's scale factors.
    • Rotate: Rotate the vertex using the node's quaternion rotation.
    • Translate: Shift the vertex by the node's translation vector.

This process ensures that the vertex is positioned accurately within the 3D scene, consistent with its defined node transformations.

Scenario 2: Influence by Joints

In cases where a vertex is influenced by joints, its final position is determined not just by the node transformations, but also by the skinning process involving joint weights and animations.

Methodology for Skinned Vertices:

  1. Fetch Original Vertex Position: Start with the position data from the POSITION attribute.
  2. Apply Joint Transformations: For each joint affecting the vertex:
    • Current Transformation: Take the joint's animated transformation from node animations.
    • Inverse Bind Matrix: Use the inverse bind matrix from the joint to transform the vertex into the joint's coordinate space.
    • Weight Application: Multiply the transformed position by the vertex’s weight for the joint to determine the joint’s influence.
  3. Aggregate Transformations: Sum the transformed positions from all influencing joints to obtain the final vertex position.

The involvement of skinning adds complexity, enabling dynamic and realistic animations within the model, as joints move and affect attached vertices.

Conclusion

Understanding vertex positioning within glTF models involves comprehending both static node transformations and dynamic skinning influenced by skeletal animations. This knowledge is crucial for 3D model rendering in various applications, ensuring accurate representation of both stationary and animated models in computer graphics.

Through these insights, developers and animators can achieve greater precision in their 3D scenes, leading to more lifelike and engaging virtual experiences.

A glTF file with skinning

Please read the comment in the json file

json
{
  "scene": 0, // Index of the default scene
  "scenes": [{
    "nodes": [0, 1] // Nodes involved in this scene
  }],

  "nodes": [{
    "skin": 0, // This node includes skinning information
    "mesh": 0 // and is associated with the first mesh
  }, {
    "children": [2] // This node has a child node (index 2), and has no transformation (identity transformation)
  }, {
    "translation": [0.0, 1.0, 0.0], // Position of the node
    "rotation": [0.0, 0.0, 0.0, 1.0] // Orientation of the node
  }],

  // Mesh data
  "meshes": [{
    "primitives": [{
      "attributes": {
        "POSITION": 1, // Reference to the position accessor (1 position for earch vertex)
        "JOINTS_0": 2, // Reference to the joints accessor (4 joint index for each vertex, each vertex can be influenced by 4 joint at most)
        "WEIGHTS_0": 3 // Reference to the weights accessor (4 joint influence weight for each vertex)
      },
      "indices": 0 // Reference to the indices accessor (describes how to connect vertex)
    }]
  }],

  // Skinning data
  "skins": [{
    "inverseBindMatrices": 4, // Reference to the accessor for inverse bind matrices (one matric for each joint)
    "joints": [1, 2] // Indices of nodes that are joints (In glTF, a 'joint' is also a node)
  }],

  // Animation data
  "animations": [{
    "channels": [{
      "sampler": 0,
      "target": {
        "node": 2, // Animation is targeted at node 2
        "path": "rotation" // Specifies which property of the node is animated
      }
    }],
    "samplers": [{
      "input": 5,
      "interpolation": "LINEAR",
      "output": 6
    }]
  }],

  // Buffer data containing the actual binary geometry and animation data
  "buffers": [{
      "uri": "data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAvwAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAvwAAAD8AAAAAAAAAPwAAAD8AAAAAAAAAvwAAgD8AAAAAAAAAPwAAgD8AAAAAAAAAvwAAwD8AAAAAAAAAPwAAwD8AAAAAAAAAvwAAAEAAAAAAAAAAPwAAAEAAAAAA",
      "byteLength": 168
    },
    {
      "uri": "data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=",
      "byteLength": 320
    }
  ],

  // Buffer views that slice the buffer data into usable sections
  "bufferViews": [{
      "buffer": 0,
      "byteLength": 48,
      "target": 34963
    },
    {
      "buffer": 0,
      "byteOffset": 48,
      "byteLength": 120,
      "target": 34962
    }
  ],

  // Accessors that interpret the buffer data
  "accessors": [{
      "bufferView": 0,
      "componentType": 5123,
      "count": 24,
      "type": "SCALAR"
    },
    {
      "bufferView": 1,
      "componentType": 5126,
      "count": 10,
      "type": "VEC3",
      "max": [0.5, 2.0, 0.0],
      "min": [-0.5, 0.0, 0.0]
    }
  ],

  "asset": {
    "version": "2.0" // glTF version
  }
}