urho3D 运动逆解Inverse Kinematics

概述

IK(逆向运动学)在许多情况下都很有用,从程序动画到动画的小调整。简单地说,当您希望将分层结构的尖端定位在已知位置,并且需要计算父关节的所有旋转以实现这一点时,将使用IK。

  示例包括:移动一只手来拾取物体,或调整一只脚,使其始终接触地面,无论倾斜程度如何。

术语

了解一些在谈论反向运动学时使用的术语是很有帮助的,这样你就能更好地理解所说的内容。

  • 解算器树中最上部的节点称为基本节点或根节点。
  • 需要移动到特定目标位置(例如:人的手)的节点称为末端效应器。
  • 多个子节点从中分支的关节称为子基节点。
  • IK解算器在节点的单个“字符串”(称为链)上工作效率最高。
  • 整个结构(在人类双手的情况下)被称为链树。

末端效应器

效应器用于设置节点的目标位置和旋转。可以通过将IKEffector组件附加到节点来创建一个。

Node* handNode = modelNode->GetChild("Hand.R", true);
 
IKEffector* effector = handNode->CreateComponent<IKEffector>();  // C++
IKEffector@ effector = handNode.CreateComponent("IKEffector");   // AngelScript
local effector = handNode:CreateComponent("IKEffector")          -- Lua

然后,可以使用IKEeffector::SetTargetPosition为效应器指定目标位置(例如,苹果的位置),也可以告诉效应器使用IKEeffect::SetTargetNode自动跟踪节点的位置。

effector->SetTargetPosition(appleNode->GetWorldPosition());  // C++
effector.targetPosition = appleNode.worldPosition;           // AngelScript
effector.targetPosition = appleNode.worldPosition            -- Lua

如果启用,还可以告诉效应器尝试使用IKEffector::SetTargetRotation匹配目标旋转。如果您希望手指向与苹果相关的特定方向,则此功能非常有用(需要在解算器中启用此功能,这将在下面进一步讨论)。

目标位置和旋转都在全局空间中设置。

另一个重要参数是链长度IKEffector::SetChainLongth。链长度为1表示单个节段或“骨骼”受到影响。手臂和腿通常使用值2(因为您只想求解手臂而不是整个身体)。默认值为0,这意味着直接到基本节点的所有节点都会受到影响。

effector->SetChainLength(2); // Humans have two bones in their arms
effector.chainLength = 2;    // AngelScript
effector.chainLength = 2     -- Lua

   效应器有一个权重参数(使用IKEffector::SetWeight),指示它们对要解决的树的影响程度。您可以利用这一点平滑地过渡到IK解决方案中和从IK解决方案过渡。当角色开始拾取对象并希望从动画平滑切换到IK时,将需要此选项。

effector->SetWeight(SomeSplineFunction());  // C++
effector.weight = SomeSplineFunction();     // AngelScript
effector.weight = SomeSplineFunction();     -- Lua

如果您使用了权重参数,您可能已经注意到它会导致目标位置的线性插值。这在有机生物身上看起来很糟糕,特别是当解决的树与原始树相距很远时。您可以考虑启用nlrp,这会导致权重围绕下一个子基关节旋转。可以使用IKEffector::SetFeature启用此功能。

effector->SetFeature(IKEffector::WEIGHT_NLERP, true);  // C++
effector.WEIGHT_NLERP = true;                          // AngelScript
effector.WEIGHT_NLERP = true                           -- Lua

请注意,脚本绑定在这方面的工作方式略有不同。这些特征可以作为属性直接在效应器对象上启用/禁用,而不必调用SetFeature。这是为了方便(但可能会因脚本API不一致而在将来更改)。

求解器

解算器负责基于附加的效应器及其设置计算解决方案。

请注意,解算器只会影响子树中的节点。这意味着,如果将IKEeffector附加到IKSolver节点的父节点,则解算器将忽略该效应器。

可以通过将IKSolver组件附加到节点来创建解算器:

IKSolver* solver = modelNode->CreateComponent<IKSolver>();  // C++
IKSolver@ solver = modelNode.CreateComponent("IKSolver");   // AngelScript
local solver = modelNode:CreateComponent("IKSolver")        -- Lua

您首先要做的是选择合适的算法。在撰写本文时,有3种算法可供选择,您应该按照此处列出的顺序选择它们。

  • ONE_BONE:专门的解算器,用于将对象指向目标位置(例如眼睛、头部等)
  • TWO_BONE:使用三角法计算直接解的专用解算器,特别是针对两个骨骼结构(手臂、腿)
  • FABRIK:一个通用的求解器,能够解决任何你能解决的问题。它使用迭代算法,因此比两个专用算法慢一点。应用于具有3个或更多骨骼的结构,或具有多个末端效应器的结构。

您可以使用以下方法设置算法:

solver->SetAlgorithm(IKSolver::FABRIK);  // C++
solver.algorithm = IKAlgorithm::FABRIK;  // AngelScript
solver.algorithm = IKSolver.FABRIK       -- Lua

   如果选择了迭代算法,那么可能还需要调整最大迭代次数和容差。FABRIK收敛速度非常快,在20次或更少的迭代中运行良好。有时,您甚至只需要5次迭代就可以成功。公差指定允许末端效应器与其目标之间的最大距离。显然,设置的公差越小,需要的迭代次数就越多。公差的良好起始值约为所求解链的100倍(例如,如果链长2个单位,则将公差设置为0.02)。

solver->SetMaximumIterations(20); // Good starting value for FABRIK
solver->SetTolerance(0.02);       // Good value is 100th of your chain length.
 
solver.maximumIterations = 20;    // AngelScript
solver.tolerance = 0.02;
 
solver.maximumIterations = 20     -- Lua
solver.tolerance = 0.02

请注意,如果选择了直接解算器(例如TWO_BONE或ONE_BONE),则这些设置不会起作用。

解算器功能

  解算器上有许多功能可以启用/禁用,所有这些功能都可以使用IKSolver::SetFeature进行切换,并使用IKSolver::GetFeature进行检查。您可以随时查看IKSolver::Feature的文档,了解每个功能的详细描述。

自动解决

    默认情况下,解算器将处于自动解算模式。这意味着它将自动为您执行其计算,以响应E_SCENEDRAWABLEUPDATEFINISHED。您所要做的就是创建效应器,设置它们的目标位置,然后让解算器处理其余的。您可以通过禁用AUTO_SOLVE功能来覆盖此行为,在这种情况下,您必须手动调用IKSolver::SOLVE才能执行任何操作。如果您希望在动画更新后,但在计算反向运动学之前“钩住”,则可能需要此选项。

solver->SetFeature(IKSolver::AUTO_SOLVE, false);  // C++
solver.AUTO_SOLVE = false;                        // AngelScript
solver.AUTO_SOLVE = false                         -- Lua

下面是如何手动调用解算器。

// C++
void MyLogic::Setup()
{
    SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(MyLogic, HandleSceneDrawableUpdateFinished));
}
void MyLogic::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
{
    GetComponent<IKSolver>()->Solve();
}
 
// AngelScript
void Setup()
{
    SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
}
void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
{
    GetComponent("IKSolver").Solve()
}
 
// Lua
function Setup()
    SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
end
function HandleSceneDrawableUpdateFinished(eventType, eventData)
    GetComponent("IKSolver"):Solve()
end

IKSolver::JOINT_ROTATIONS

solver->SetFeature(IKSolver::JOINT_ROTATIONS, false);  // C++
solver.JOINT_ROTATIONS = false;                        // AngelScript
solver.JOINT_ROTATIONS = false                         -- Lua

   如果在蒙皮模型(或需要旋转的节点结构)上使用IK,则应启用此选项。如果您不关心节点旋转,则可以禁用此功能并获得少量性能提升。

   禁用时,所有节点都将保持其在世界中的原始方向,只有其位置会更改。

   解算器通过将解算的树与原始树进行比较来计算解算收敛后的关节旋转,以此作为计算增量角的方法。然后将它们乘以原始旋转,以获得最终关节旋转。

目标_旋转

solver->SetFeature(IKSolver::TARGET_ROTATIONS, false);  // C++
solver.TARGET_ROTATIONS = false;                        // AngelScript
solver.TARGET_ROTATIONS = false                         -- Lua

启用此选项将导致在求解过程中考虑效应器节点(IKEffector::SetTargetRotation)的方向。这意味着效应器节点将尽可能地匹配目标的旋转。如果目标不在可及范围内或刚好在可及的范围内,则链条将伸出并开始忽略目标旋转,以达到其目标。

禁用此功能会导致IKEffector::SetTargetRotation无效。

UPDATE_ORIGINAL_POSE, UPDATE_ACTIVE_POSE, 和 USE_ORIGINAL_POSE

这些选项很难理解。

解算器实际上存储两棵树,而不是一棵。有一个活动的树,有点像“工作台”。解算器使用活动树作为其初始条件,但也将解写回活动树中(即,在适当位置解算树,而不是克隆树)。

然后是原始树,它在创建过程中设置一次,然后从未更改(至少在默认情况下不会更改)。

您可以控制解算器应将哪棵树用于其初始条件。如果启用USE_ORIGINAL_POSE,则解算器将在解算之前首先将所有位置/旋转从原始树复制到活动树中。因此,如果可以的话,解决方案将倾向于“恢复”到其原始配置。

如果禁用USE_ORIGINAL_POSE,则解算器将使用活动树。活动树将包含上次解决的任何姿势。因此,解决方案将趋于更加“连续”。

非常重要:请注意,除非启用UPDATE_active_POSE(默认情况下启用),否则Urho3D不会更新活动树。如果UPDATE_ACTIVE_POSE被禁用,则任何已移动到IKSolver控制之外的节点都将被有效忽略。因此,如果模型已设置动画,则很可能需要启用此选项。

    UPDATE_ORIGINAL_POSE实际上不是必需的,但这里是用于调试的。您可以通过启用此功能或显式调用IKSolver::ApplySceneToOriginalPose来更新原始姿势。

约束

此功能尚未实现,计划在未来发布。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乘小舟舟,游大世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值