Blender中对于骨骼的各种Matrix表示

0 前导知识

Armature

Blender中,Armature的概念等价于Graphics中的Skeleton,一个Armature由若干个Bone组成,不同Bone之间可以建立父子关系,形成层级结构。父骨骼移动时,子骨骼会跟随。Bone可以通过变换(Transform)驱动物体变形。关于Skeletal Animation更多技术细节请参见这里的class 11, 12, 13。

Interaction Mode

Object Mode: 管理/移动场景中整个对象

Edit Mode:用于修改骨骼结构,如添加/删除骨骼、调整层级;

Pose Mode: 该模式下允许调整骨骼姿势,如旋转骨骼、添加关键帧动画;

Pose Bone vs Data Bone

  Pose Bone Data Bone
模式 Pose Mode Edit Mode
作用 控制动画、添加约束、插入关键帧 定义骨骼结构、名称、父子关系
是否影响动画 ✅ 是,会影响最终动画 ❌ 否,仅用于骨骼结构
是否可变形 ✅ 是,可用于角色变形 ❌ 否,不影响角色变形
是否存储关键帧 ✅ 是,存储关键帧动画数据 ❌ 否,不存储动画数据
可否添加约束 ✅ 可以(如 IK、Track To) ❌ 不能

Space Types

Blender中,骨骼的变换涉及多个坐标空间,不同的空间用于不同的计算方式和约束规则,因此衍生出了一系列space[1],本质上分为local space和world space两种。object/armature/bone space是local space的特例。

接下来是特化space:

Pose Space

Bone transform相对于rest pose,表示骨骼在pose mode下的变换。每个bone transform是基于它在rest pose下的位置和朝向计算的,与armature的 object transform无关。

pose space是pose mode使用的主要space,也是关键帧存储bone transform的space。

Local with Parent

bone transform相对于parent bone的transform。参考的是parent bone的local coordinates,也就是说,bone transform受parent bone影响。

如果parent bone是root bone,那么local with parent等价于pose space。

Object Space

Object(Mesh、Armature等)的local space。对armature而言,object space就是armature space。如果armature在object mode下有transform,它的骨骼变换也会受到影响。

Bone Space

bone的local space,即bone的origin是它本身的head,坐标轴沿着bone direction。

Armature Space

等同于object space,但特指armature这个object的local space。整个Armature相对于世界坐标的变换发生在这个空间里。

1 Matrix

该章节大篇幅参考了[3]。

armature = bpy.data.objects['Armature']
data_bone = armature.data.bones["Bone"]
matrix_local = data_bone.matrix_local
armature = bpy.data.objects['Armature']
armature.data.bones["Root"].matrix
armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones["Bone"]
matrix_basis = pose_bone.matrix_basis

子骨骼的matrix_basis与父骨骼的matrix复合可得到子骨骼在pose space下的变换矩阵。

armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones["Bone"]
# bone有parent
pose_bone.matrix == pose_bone.parent.matrix @ pose_bone.matrix_basis
# bone无parent
pose_bone.matrix == pose_bone.bone.matrix_local @ pose_bone.matrix_basis
armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones["Bone"]
pose_bone.matrix == pose_bone.matrix_channel @ pose_bone.bone.matrix_local
armature = bpy.data.objects['Armature']
matrix_world = armature.matrix_world

获取armature space下的变换矩阵:

def matrix_armature(armature: bpy.types.Object, bone_name: str):
    matrix_local = armature.data.bones[bone_name].matrix_local
    matrix_basis = armature.pose.bones[bone_name].matrix_basis

    parent_bone = armature.pose.bones[bone_name].parent

    if parent_bone is None:
        return matrix_local @ matrix_basis
    else:
        parent_matrix_local = armature.data.bones[parent_bone.name].matrix_local
        return matrix_armature(armature, parent_bone.name) @ (parent_matrix_local.inverted() @ matrix_local) @ matrix_basis 

从bone space逐级变换到parent的bone space,最终达到root bone的bone space即为armature space。

References

[1] https://docs.blender.org/manual/en/latest/animation/constraints/interface/common.html#space-types

[2] https://blenderartists.org/t/what-is-different-local-space-and-pose-space/578162/5

[3] https://maoxyzt.github.io/Notes/Software-Specific/Blender/blender%E4%B8%AD%E5%90%84%E7%A7%8DMatrix%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB.html#_1-matrix