Godot - 骨骼不在正确的轴上旋转

Godot - Bone doesn't rotate on the correct axis

我正在尝试将 Godot 中的骨骼旋转一个我已经计算出的角度和轴。我已经通过旋转网格实例测试了这个角度和轴,它运行良好。我检查了计算结果,它们看起来是正确的(轴是通过计算构成角度的两个向量的叉积找到的)。然而,当我尝试以相同的值旋转骨骼时,它旋转了正确的角度,但轴突然不同了(它应该向上旋转,但它向左旋转)。这是我的代码有问题,还是 3d model/skeleton 制作不正确?

这是我的代码:

# the vector is the Mesh Instance, and the rotation is correct
vector.rotate(axis, angle)

# the bone is rotated by the same values, but the axis is wrong
bonePose = skel.get_bone_pose(id)
bonePose = bonePose.rotated(axis, angle)
skel.set_bone_pose(id, bonePose)

您可以编写全局姿势覆盖:

bonePose = skel.get_bone_global_pose(id)
bonePose = bonePose.rotated(axis, angle)
skel.set_bone_global_pose_override(id, bonePose, 1.0)

第三个参数amount是权重。如果它是 0,它会保持姿势不变,如果它是 1.0,它会替换它。介于两者之间的值是插值。还有第四个可选参数 persistent,它指定是否应该在下一次更新骨架时保持姿势覆盖(它应该使用更新后的姿势计算插值)。

如果这对你有用,那就太好了。


对我来说,回答问题更有趣的是如何在没有 set_bone_global_pose_override 的情况下做到这一点。 将此视为学术练习。

骨骼的全局姿态是父骨骼的全局姿态乘以骨骼的姿态。让我们表达一下:

b.global_pose = parent.global_pose * b.pose

其实如果有自定义pose的话就是:

b.global_pose = parent.global_pose * b.custom_pose * b.pose

如果有休息姿势,那就是:

b.global_pose = parent.global_pose * b.rest_pose * b.custom_pose * b.pose

而且我们要修改全局pose,但是只能控制自定义pose和pose。

事实上,没有理由修改姿势,因为对姿势应用变换等同于设置自定义姿势。

现在,问题是我们可以设置什么自定义姿势 X,因此它相当于对全局姿势应用变换 T

T * b.global_pose = parent.global_pose * b.rest_pose * X * b.pose

求解 X。提醒:变换乘法不可交换。

首先在两侧应用父级全局姿势的反转(它们在右侧取消):

inverse(parent.global_pose) * T * b.global_pose = b.rest_pose * X * b.pose

然后左右摆姿势:

inverse(b.rest_pose) * inverse(parent.global_pose) * T * b.global_pose = X * b.pose

然后两边的pose反转:

inverse(b.rest_pose) * inverse(parent.global_pose) * T * b.global_pose * inverse(b.pose) = X

这就是我们需要如何计算新的自定义姿势。其代码将是这样的:

var inverse_rest = skel.get_bone_rest(id).affine_inverse()

var parent = ske.get_bone_parent(id)
var inverse_parent_global = Transform.IDENTITY
if parent != -1:
    inverse_parent_global = skel.get_bone_global_pose(parent).affine_inverse()

var global_pose = skel.get_bone_global_pose(id)

var inverse_pose = skel.get_bone_pose(id).affine_inverse()

var t = Transform.IDENTITY.rotated(axis, angle) # the operation you want to do

var x = inverse_rest * inverse_parent_global * t * global_pose * inverse_pose

skel.set_bone_custom_pose(id, x)

或者如果你想设置姿势而不是自定义姿势(假设自定义仍然是身份转换):

var inverse_rest = skel.get_bone_rest(id).affine_inverse()

var parent = ske.get_bone_parent(id)
var inverse_parent_global = Transform.IDENTITY
if parent != -1:
    inverse_parent_global = skel.get_bone_global_pose(parent).affine_inverse()

var global_pose = skel.get_bone_global_pose(id)

var t = Transform.IDENTITY.rotated(axis, angle) # the operation you want to do

var x = inverse_rest * inverse_parent_global * t * global_pose

skel.set_bone_pose(id, x)

还有另一种方法可以解决使用 .set_bone_global_pose_override() 带来的拉伸问题。事实证明轴在计算它的全局 space 中是正确的,但对于骨骼来说是错误的,因为它相对于骨骼局部旋转。当轴从全局 space 转换为骨骼的局部 space 时,旋转符合我的预期。我仍然不确定为什么其他方法会导致拉伸。

代码如下:

# transform from global space to local space
var bone_to_global = skel.global_transform * skel.get_bone_global_pose(bone)
var axis_local = bone_to_global.basis.transposed() * axis_global

# rotate the bone
bonePose = skel.get_bone_pose(bone)
bonePose = bonePose.rotated(axis_local.normalized(), angle)
skel.set_bone_pose(bone, bonePose)