参考:Two Bone IK

什么是Two Bone IK

其实就是最简单的IK问题了,有三个Joint A、B和C组成的BoneChain A->B->C,在A的位置不变的情况下,通过改变A和B的旋转,把C挪到目标的位置点,UE里提供了这么个动画节点,如下图所示:

此节点应该只能在动画蓝图里使用,这里的Effector指的是BoneChain的最尾部的Joint,这个节点的输入有:

  • Effector Location: BoneChain的最尾部的Joint的位置,对应坐标系需要自行选择
  • Joint Target Location:注意它并不是Middle Joint的目标位置,而是帮助算出BoneChain的Middle Joint所在的平面的点,对应坐标系需要自行选择
  • 输入的Component Space下的Pose
  • Alpha值,Alpha从0平滑过渡到1的过程其实就是IK算法平滑应用上去的过程

还有个重要数据,就是在Two Bone IK的Details栏里指定需要Effector对应的Bone,UE会自动找到其Parent和Parent的Parent骨骼,如下图所示:

IK的本质就是在Bone Chain里,改变除了Effector以外其他Joint的Rotation数据,而实际编辑时,改变的是除了Start Joint以外其他Joint的Transition数据,如下图所示:

所谓的IK,就是根据这些Joint的坐标,反算出Joint的Rotation数据

关于Joint Target Location
注意它并不是Middle Joint的目标值,可以试想一下,在Two Bone IK过程中,不考虑特殊情况时,三个Joint里,起点和终点的坐标是固定的,此时只要求出Middle Bone的坐标即可。但其实此时的Middle Joint的坐标的解是有无穷多个的,所以此时,额外规定了一个Joint Target Location的坐标,它用于表示,Middle Joint的坐标会在IK Effector、RootPos和Joint Target Location三个点组成的平面上

可以看看这个视频Knee Pole Vectors,里面拖拽的就是Joint Target Location

设置Two Bone IK

主要是设置两个地方:

  • 设置IK Bone,UE会根据设置的IK Bone,沿着BoneChain往上寻找两代,找到对应的三个Joint
  • 设置调整IK Goal的方式和坐标

设置世界坐标下的IK Goal
Effector Local Space选择World Space,然后调整相应的坐标即可,这个Space只会影响输入的Effector Location,不会影响下面的Joint Target Location,我这里选择hand_rIK Bone,如下图所示:

也可以改成Bone Space下的Effector Local Space类型,此时就会记录相对于特定Bone坐标的Offset了,这里需要指定对应参考Bone的坐标,我这里仍然选择hand_rEffector Target,此时的Gizmos会直接绘制在要改变的关节上,如下图所示:

Joint Target Location的设置
Two Bone IK Chain里不止能调整End Bone,也可以调整Middle Bone的坐标,这里的Joint Target Location也有自己设置的类型,不过需要注意的是,根据这篇文章,Joint Target Location是不支持World Space和Component Space模式的,这两种模式下,设置该值会不起作用,如下图所示:

左上角的图还有个小的gizmos,应该绘制的是Joint Target Location,好像不让拖拽

Two Bone IK的原理

可以先来列举一下问题,我有三个Joint的坐标,它们是已知的,分别为RootPos、JointPos和EndPos,如下图红线所示,假设我要移动JointPos的位置,让它变到IOutJointPos点,此时我三个Joint的坐标变为RootPos、OutJointPos和EffectorPos,图中还有一个指定的JointTargetPos点,它与这三个点在同一个平面上:


那么如何求解出OutJointPos的值呢,这里有以下信息(Tips):

  1. BoneChain在IK应用前后的骨骼长度是不会变的
  2. 当三角形的三条边的长度已知时,三角形的三个角度都是可以求出来的
  3. Root Pos、OutJointPos、JointTargetPos和Effector Pos的坐标都在同一平面上,除了OutJointPos,其他坐标都是已知的

这里把图简化一下,如下图所示,注意下面的点都是三维空间的点:

这里可以把三角形的边连起来,如下图所示,同时作一条OutJointPos往对边的垂线,垂点为P:

可以得到:OutJointPos的坐标等于:RootPos的坐标加上向量RootPos->P,再加上向量P->OutJointPos,这俩向量都好算,这里有边RootPos到OutJointPos,设长度为A,向量的大小分别是cos(α) * Asin(α) * A,向量的方向也好算,一个是RootPos到EffectorPos的单位向量,另外一个是算出垂直于RoootPos-EffectorPos线段, 且指向JointTargetPos方向的垂线方向即可,计算代码如下:

// 计算DesiredDir向量, 它代表RootPos指向EffectorOis的方向
FVector DesiredDir = (DesiredPos - RootPos).GetSafeNormal;// 计算JointTargetDelta向量, 它代表RootPos指向JointTargetPos的向量
// Get joint target (used for defining plane that joint should be in).
FVector JointTargetDelta = JointTarget - RootPos;// 这里的DesiredDir为单位向量, JointTargetDelta不是单位向量,  | 符号代表点乘
// ((JointTargetDelta | DesiredDir) * DesiredDir)算出的是RootPos->DesiredPos在RootPos->JointTarget上的投影向量
// 算出垂直于RoootPos-EffectorPos线段, 且指向JointTargetPos方向的垂线方向, 即JointBendDir
JointBendDir = JointTargetDelta - ((JointTargetDelta | DesiredDir) * DesiredDir);
// 算出垂线方向
JointBendDir.Normalize();

相关代码核心基本都放在AnimationCore::SolveTwoBoneIK这个静态函数里了,执行地方是在FAnimNode_TwoBoneIK::EvaluateSkeletalControl_AnyThread函数里,会在动画节点的Evaluate阶段,在Input动画Evaluate之后,调用此函数执行类似于Pose后处理的操作。

核心函数会最后算出新的OutJointPos的坐标值,不过IK过程本质改变的是Joint的旋转数据,应该后面会用类似Quaterion.FromToRotation函数算出BoneChain的Parent和MiddleJoint的DeltaRotation吧

看了下代码,确实是这样:

void SolveTwoBoneIK(FTransform& InOutRootTransform, FTransform& InOutJointTransform, FTransform& InOutEndTransform, const FVector& JointTarget, const FVector& Effector, float UpperLimbLength, float LowerLimbLength, bool bAllowStretching, float StartStretchRatio, float MaxStretchScale)
{FVector OutJointPos, OutEndPos;FVector RootPos = InOutRootTransform.GetLocation();FVector JointPos = InOutJointTransform.GetLocation();FVector EndPos = InOutEndTransform.GetLocation();// IK solverAnimationCore::SolveTwoBoneIK(RootPos, JointPos, EndPos, JointTarget, Effector, OutJointPos, OutEndPos, UpperLimbLength, LowerLimbLength, bAllowStretching, StartStretchRatio, MaxStretchScale);// IK解算完后, 改变joint的rotation, 由于可能有骨骼伸缩, 这里还要改变骨骼长度// Update transform for upper bone.{// Get difference in direction for old and new joint orientationsFVector const OldDir = (JointPos - RootPos).GetSafeNormal();FVector const NewDir = (OutJointPos - RootPos).GetSafeNormal();// Find Delta Rotation take takes us from Old to New dirFQuat const DeltaRotation = FQuat::FindBetweenNormals(OldDir, NewDir);// Rotate our Joint quaternion by this delta rotation// 注意DeltaRotation是左乘InOutRootTransform.SetRotation(DeltaRotation * InOutRootTransform.GetRotation());// And put joint where it should be.InOutRootTransform.SetTranslation(RootPos);}// update transform for middle bone{// Get difference in direction for old and new joint orientationsFVector const OldDir = (EndPos - JointPos).GetSafeNormal();FVector const NewDir = (OutEndPos - OutJointPos).GetSafeNormal();// Find Delta Rotation take takes us from Old to New dirFQuat const DeltaRotation = FQuat::FindBetweenNormals(OldDir, NewDir);// Rotate our Joint quaternion by this delta rotationInOutJointTransform.SetRotation(DeltaRotation * InOutJointTransform.GetRotation());// And put joint where it should be.InOutJointTransform.SetTranslation(OutJointPos);}// Update transform for end bone.// currently not doing anything to rotation// keeping input rotation// Set correct location for end bone.InOutEndTransform.SetTranslation(OutEndPos);
}

允许骨骼伸缩的Two Bone IK

先看一下UE里相关的参数:

  • Allow Stretching: When enabled, stretching of the set two-bone chain will be allowed. You can set the limits of the stretching in the Start Stretch Ratio and the Max Stretch Scale properties.
  • Start Stretch Ratio: When the Allow Stretching property is enabled, you can set the threshold to control when the two-bone chain is able to begin stretching. This value determines when to start stretching. For example, 0.9 means once it reaches 90% of the whole length of the limb, it will start to apply a stretch to the structure.
  • Max Stretch Scale:When the Allow Stretching property is enabled, you can set the limit to control the maximus scale of the stretch allowed for the structure. This value determines what is the max stretch scale. For example, 1.5 means it will stretch until 150% of the whole length of the limb.

结合代码,设实际骨骼链长度与预期IK链长度的比例为ReachRadio,我总结了以下规则:

  1. 这个过程只允许骨骼伸长,不允许骨骼缩短
  2. Max Stretch Scale参数很好理解,代表骨骼最多被伸长的比例
  3. Start Stretch Ratio参数稍微麻烦一点,代表骨骼允许被伸长的比例上限
  4. Start Stretch Ratio可以小于1,此时若ReachRadio在(Start Stretch Ratio, 1)范围内,骨骼也会被伸长,具体伸长的比例后面代码里会有

具体到代码,先是计算骨骼比例ReachRadio,如下所示:

// 计算预期骨骼链长度与实际骨骼链长度的比例
const float ReachRatio = DesiredLength / MaxLimbLength;

然后计算[Start Stretch Ratio, Max Stretch Ratio]这个区间范围的值:

// StartStretchRatio和MaxStretchScale都是在IK部分设置的值, 默认为1.0和2.0
const float ScaleRange = MaxStretchScale - StartStretchRatio;

然后需要设计一个算法,这个算法需要满足以下条件:

  • 算法只能伸长骨骼的长度,不能缩短
  • 当ReachRatio小于Start Stretch Ratio时,骨骼长度不做任何改变
  • 当ReachRatio大于Max Stretch Ratio时,骨骼长度乘以Max Stretch Ratio
  • 当ReachRatio在二者范围之间时,根据设计的算法进行骨骼长度的伸长。算法思路是,当ReachRatio为Start Stretch Ratio时,骨骼长度不变,当ReachRadio为Max Stretch Ratio时,骨骼长度乘以Max Stretch Ratio,其他值时比例均匀增长

整体代码如下,就是这里计算ScalingFactor的算法稍微有一点绕:

if (bAllowStretching)
{// StartStretchRatio和MaxStretchScale都是在IK部分设置的值, 默认为1.0和2.0const float ScaleRange = MaxStretchScale - StartStretchRatio;if (ScaleRange > KINDA_SMALL_NUMBER && MaxLimbLength > KINDA_SMALL_NUMBER){// 计算预期骨骼链长度与实际骨骼链长度的比例const float ReachRatio = DesiredLength / MaxLimbLength;// 下面这个算法有点绕, 总之是让(1 + ScalingFactor)成为BoneChain的长度变化系数, 同时防止骨骼长度变小的情况// FMath::Clamp<float>((ReachRatio - StartStretchRatio) / ScaleRange, 0.f, 1.f)是算出等比例变化的量// 当ReachRatio为StartStretchRatio时, 返回0, 当ReachRatio为MaxStretchScale时, 返回1// 再乘以(MaxStretchScale - 1)  是算出等比例伸长的增量const float ScalingFactor = (MaxStretchScale - 1.f) * FMath::Clamp<float>((ReachRatio - StartStretchRatio) / ScaleRange, 0.f, 1.f);// 当MaxStretchScale小于1时, 这里的ScalingFactor < 0, 此时的UpperLimbLength等信息不会改变if (ScalingFactor > KINDA_SMALL_NUMBER){// ScalingFactor大于0, 所以这里在AllowStretching时, 骨骼只可能变长, 不可能变短LowerLimbLength *= (1.f + ScalingFactor);UpperLimbLength *= (1.f + ScalingFactor);MaxLimbLength *= (1.f + ScalingFactor);}}
}

我自己写了个算法,感觉更容易理解一些,其实计算过程是一样的:

if (bAllowStretching)
{// StartStretchRatio和MaxStretchScale都是在IK部分设置的值, 默认为1.0和2.0const float ScaleRange = MaxStretchScale - StartStretchRatio;if (ScaleRange > KINDA_SMALL_NUMBER && MaxLimbLength > KINDA_SMALL_NUMBER){// 计算预期骨骼链长度与实际骨骼链长度的比例const float ReachRatio = DesiredLength / MaxLimbLength;float ScalingFactor = FMath::Clamp<float>((ReachRatio - StartStretchRatio) / ScaleRange, 0.f, 1.f);// 把ScalingFactor从[0,1]区间映射到[1, MaxStretchScale]区间ScalingFactor = Mathf.Map(1, MaxStretchScale, ScalingFactor);// 返回1 + (MaxStretchScale - 1) * ScalingFactor // 当MaxStretchScale小于1时, 这里的ScalingFactor < 0, 此时的UpperLimbLength等信息不会改变if (ScalingFactor > KINDA_SMALL_NUMBER){LowerLimbLength *= ScalingFactor;UpperLimbLength *= ScalingFactor;MaxLimbLength *= ScalingFactor;}}
}

关于JointTarget

或者说PoleVector,我看了下,很多TwoBoneIK里面就把原本的MiddleBone的位置作为新的JointTarget的平面所在点,此时Parrent指向Middle Bone的向量是不变的,Upper Bone此时只会产生Twist旋转

Two Bone IK里的Twist

这一块我还不太清楚,等我研究完动画系统里的Twist机制,再补充这块知识

UE的Two Bone IK相关推荐

  1. 虚幻引擎图文笔记:用Two Bone IK实现手扶墙

    效果 文章目录 效果 原理 步骤 角色蓝图 动画蓝图 Event Graph Amin Graph 运行一下 原理 在<虚幻引擎插件:使用Power IK轻松愉快地实现脚底板位置矫正>一文 ...

  2. 跨境电子商务及支付业务管理体系的构建

    一.我国跨境电子商务及支付交易现状 1.跨境电子商务起步晚增速快 2011年在全球经济增长放缓背景下,我国跨境电子商务小额出口业务的总体规模超过100亿美元,虽仅占2011年全国出口总额的0.5%,但 ...

  3. [玩转UE4/UE5动画系统>应用篇>功能模块] 之 Foot IK系统(ALS V4实现方案详解)

    本教程采用图文教程+视频教程的多元化形式,我会为不同的知识点选择适当的表达方式. 教程内容将同步免费发布于 开发游戏的老王(知乎|CSDN)的专栏<玩转UE4/UE5动画系统>. 教程中使 ...

  4. UE4 脚部IK Foot Placement 的实现

    首先是思路 Foot Placement 其实就是通过IK动画调整骨骼把游戏角色的双脚固定在地面上,确保角色在斜坡或者凹凸的地形上时可以正确的将脚放 置在其表面上 : 文档链接 : https://d ...

  5. [Unity] 使用 Animation Rigging 制作瞄准 IK 1

    1. 安装 Animation Rigging Package 官方的,直接在 package manager 里面就能看到 2. 配置 Rig 先点击角色 Perfab 打开 Perfab 点击菜单 ...

  6. 项目实训- 基于unity的2D多人乱斗闯关游戏设计与开发(十1、FPS多人化——IK)

    目录 一.前言 二.前期知识 配置 三.使用IK控制武器 四.使用IK控制角色Aim 五.使用Photon同步IK约束 一.前言 需要制作枪口的同步,即枪口朝向的同步和枪械的绑定,这里使用IK 二.前 ...

  7. UE4 脚部IK实现

    脚步IK的目的是让角色的脚能够贴合地面,而不会因为胶囊体的缘故在上下坡时处于悬空状态,要实现IK的思路如下: 左边是没有IK的状态,由于胶囊体与墙面的阻挡,角色的双脚处于与地面平行的状态,两只脚不会与 ...

  8. 2022.5.7 腾讯天美暑期实习(更新中)

    2022.5.7 一面(30min) Q1:上来简单自我介绍 Q2:你玩过什么游戏? A:主机和单机游戏比较多吧(哈哈),老头环黑魂123只狼血源,忠实的魂玩家:巫师123赛博朋克2077,美国末日1 ...

  9. 10.1418 西山居 游戏客户端 一面40分钟+二面1h

    原帖在牛客,被封,不知道为什么.很无语,我只是记录过程而已,起码给个理由吧? 作者:BBBourne 链接:10.14&18 西山居 游戏客户端 一面40分钟+二面1h_笔经面经_牛客网 来源 ...

最新文章

  1. iOS开篇——UI之UIStepper (计步器)
  2. 2019年终总结一下吧
  3. [SAP ABAP开发技术总结]ABAP读写、解析XML文件
  4. DHT(Distributed Hash Table,分布式哈希表)
  5. 每天定时打开某个网页_纯技术分享,不借助任何工具下载网页中的视频、音乐等~...
  6. python-enurmate
  7. pyqt5 yolov4实现车牌识别系统
  8. 【工业大数据】35页PPT讲解:工业大数据特点、价值及其计算
  9. Vbs脚本编程简明教程之五
  10. 明翰英语教学系列之雅思常见词汇与固定搭配篇V1.0(持续更新)
  11. 聚宽macd底背离_看图学大级别MACD底背离
  12. Linux gre tunnel 端口,两台Linux通过GRE tunnel的隧道实现互通 — 并且改变其中一台的回程路由...
  13. 网络爬虫——原理简介
  14. 用php打印出日历_PHP实现简单的日历程序
  15. 东方博宜OJ 1265 - 【入门】爱因斯坦的数学题
  16. SSM配置地狱?一篇整合模板迅速解决!【建议收藏】
  17. 微信公众号消息列表首次大改版!自媒体“均贫富”前夜?
  18. python中reversed是什么意思_Python reversed函数及使用方法解析
  19. CSAPP:第12章 并发编程
  20. CTF 关于ZIP解题过程

热门文章

  1. 如何寻找出微信官方的微信指数API ?
  2. 感染[熊猫烧香变种 spoclsv.exe]
  3. 华南区高校第四届“CDA杯”数据分析大赛圆满结束
  4. 用滚动数组求解0/1背包问题
  5. excel学习(一)——规范数据、函数
  6. 希尔排序java写法_Java实现希尔排序
  7. 中国油田化学品市场预测及战略研究报告(2022版)
  8. 什么是主动攻击和被动攻击?各有何特点?
  9. 自然语言处理NLP星空智能对话机器人系列:深入理解Transformer自然语言处理 Training a GPT-2 language model
  10. leetcode唯一摩尔斯密码词(804)