文章目录
- 一、引言
- 二、骨骼控制
- 三、UE蓝图中提供的骨骼控制节点——AnimDynamics动画蓝图节点
- 1、什么是AnimDynamics动画蓝图节点
- ①使用盒体计算惯性
- ②使用约束来限制移动
- 2、AnimDynamics节点的几种常用例子
- ①单骨骼模拟
- ②骨骼链模拟 <h2 id=1>
- ③群魔乱舞(这是错误示范)
- ④平面限制
- ⑤球体限制
- 四、引用
一、引言
第一印象:已经有这么多动画混合方式了,为什么还有这么多骨骼控制节点,带着这个疑问往下看
二、骨骼控制
骨骼控制: 顾名思义就是直接控制角色的骨架,举个残酷的例子,一个人下半身截肢了并且安上了机械假肢,此时下半身的运动就不再受身体的控制,而是由机器及其里面的逻辑控制。
骨骼控制的概念也是如此,控制角色身上的某部分骨骼,使其不再受角色整体的运动(或者说既定的动画)运动,而是由一套独立的逻辑去控制。
当然了,游戏中大概是不会真的直接把下半身“截肢”的,但是可以利用骨骼控制来模拟部分不受身体驱动,或者既定动画不能完美展示的部分,比如飘动的头发。
三、UE蓝图中提供的骨骼控制节点——AnimDynamics动画蓝图节点
1、什么是AnimDynamics动画蓝图节点
AnimDynamics动画蓝图节点 是一种 轻量级的物理模拟 解决方案,它能让角色的部分骨骼网格体实现基于物理的附属动画。
看到这里,其实已经能够区分出开头的问题——动画融合实际上是将两个动画按照一定的函数运算进行混合叠加;而骨骼控制能够控制骨骼按照物理规律进行运动。前者某种程度上来说还是既定动画,而后者是基于现实世界的物理,与外界有交互的。
在UE的官方文档中对“AnimDynamics节点”给出了如下两行说明,通过代码来简要分析一下原因
需要看代码了解一下上面说的两个事
①使用盒体计算惯性
EvaluateSkeletalControl_AnyThread 中进行模拟,并对物理进行初始化InitPhysics,需要注意的是虽然EvaluateSkeletalControl_AnyThread是逐帧去tick的,但是InitPhysics在整个过程中只会被调用一次。
InitPhysics中对盒体进行初始化,在骨骼链模拟 还有解析
// AnimNode_AnimDynamics.cpp line:673
void FAnimNode_AnimDynamics::InitPhysics(FComponentSpacePoseContext& Output)
{
...
for (FAnimPhysBodyDefinition& PhysicsBodyDef : PhysicsBodyDefinitions)
{
TArray<FAnimPhysShape> BodyShapes;
BodyShapes.Add(FAnimPhysShape::MakeBox(PhysicsBodyDef.BoxExtents));
PhysicsBodyDef.BoundBone.Initialize(BoneContainer);
FTransform BodyTransform = GetBoneTransformInSimSpace(Output, PhysicsBodyDef.BoundBone.GetCompactPoseIndex(BoneContainer));
BodyTransform.SetTranslation(BodyTransform.GetTranslation() + BodyTransform.GetRotation().RotateVector(PhysicsBodyDef.LocalJointOffset)); // Transform for physics body in Sim Space.
FAnimPhysLinkedBody NewChainBody(BodyShapes, BodyTransform.GetTranslation(), PhysicsBodyDef.BoundBone);
FAnimPhysRigidBody& PhysicsBody = NewChainBody.RigidBody.PhysBody;
PhysicsBody.Pose.Orientation = BodyTransform.GetRotation();
PhysicsBody.PreviousOrientation = PhysicsBody.Pose.Orientation;
PhysicsBody.NextOrientation = PhysicsBody.Pose.Orientation;
PhysicsBody.CollisionType = PhysicsBodyDef.CollisionType;
...
}
...
}
②使用约束来限制移动
UpdateLimits 中进行 “角速度”、“线速度” 以及后面提到的 “平面限制”、“球体限制” 等限制的更新,在每次tick的时候都会更新。
在看下面代码之前需要了解存储限制的数据结构:
// AnimNode_AnimDynamics.h line:87
struct FAnimPhysConstraintSetup
{
...
FAnimPhysConstraintSetup()
: LinearXLimitType(AnimPhysLinearConstraintType::Limited)
, LinearYLimitType(AnimPhysLinearConstraintType::Limited)
, LinearZLimitType(AnimPhysLinearConstraintType::Limited)
, bLinearFullyLocked(false)
, LinearAxesMin(ForceInitToZero)
, LinearAxesMax(ForceInitToZero)
...
}
// AnimNode_AnimDynamics.h line:238
struct FAnimPhysBodyDefinition
{
...
FAnimPhysConstraintSetup ConstraintSetup;
...
}
也就是 FAnimPhysBodyDefinition ->FAnimPhysConstraintSetup ->(LinearAxesMin/LinearAxesMax)、(AngularLimitsMin/AngularLimitsMax)这样的一种结构
// AnimNode_AnimDynamics.cpp line:858
void FAnimNode_AnimDynamics::UpdateLimits(FComponentSpacePoseContext& Output)
{
...
const FAnimPhysBodyDefinition& PhysicsBodyDef = PhysicsBodyDefinitions[ActiveIndex];
...
if (PhysicsBodyDef.ConstraintSetup.bLinearFullyLocked)
{
// Rather than calculate prismatic limits, just lock the transform (1 limit instead of 6)
FAnimPhys::ConstrainPositionNailed(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset);
}
else
{
// 线速度
if (PhysicsBodyDef.ConstraintSetup.LinearXLimitType != AnimPhysLinearConstraintType::Free)
{
FAnimPhys::ConstrainAlongDirection(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset, ShapeTransform.GetRotation().GetAxisX(), FVector2D(PhysicsBodyDef.ConstraintSetup.LinearAxesMin.X, PhysicsBodyDef.ConstraintSetup.LinearAxesMax.X));
}
...
}
...
// 平面限制
if(PlanarLimits.Num() > 0 && bUsePlanarLimit)
{
for(FAnimPhysPlanarLimit& PlanarLimit : PlanarLimits)
{
...
FAnimPhys::ConstrainPlanar(NextTimeStep, LinearLimits, &RigidBody, LimitPlaneTransform);
}
}
// 球体限制
if(SphericalLimits.Num() > 0 && bUseSphericalLimits)
{
for(FAnimPhysSphericalLimit& SphericalLimit : SphericalLimits)
{
...
switch(SphericalLimit.LimitType)
{
case ESphericalLimitType::Inner:
FAnimPhys::ConstrainSphericalInner(NextTimeStep, LinearLimits, &RigidBody, SphereTransform, SphericalLimit.LimitRadius);
break;
case ESphericalLimitType::Outer:
FAnimPhys::ConstrainSphericalOuter(NextTimeStep, LinearLimits, &RigidBody, SphereTransform, SphericalLimit.LimitRadius);
break;
default:
break;
}
}
}
}
2、AnimDynamics节点的几种常用例子
可以通过为骨骼添加 AnimDynamics动画蓝图节点,进而让骨骼控制的区域不再“死气沉沉”。下面以UE自带的 欧若拉资源 展示。
①单骨骼模拟
1、仔细观察下面的头发,可以看到头发是没有动画的,仅仅是跟随着头部运动,下面我们为其添加上 AnimDynamics节点
2、再看下面角色头部 左侧的第一缕头发, 可以看到头发随着人物的运动而晃动,飘逸起来了!
3、添加的碰撞盒位置如下
4、配置如下(没有勾选链条!)
②骨骼链模拟
1、没有给骨骼链添加 AnimDynamics节点 时的样子
2、给添加骨骼链添加 AnimDynamics节点 后,左侧头发随风飘扬
3、添加的碰撞盒位置如下
4、配置如下
在使用骨骼链的时候要注意,在UE文档中明确表明了一下:
结合代码来看:在初始化物理的时候会根据骨骼链上的骨骼数进行迭代产生对应数量的碰撞盒,因此有更高的物理消耗(PhysicsBodyDefinitions中存放了骨骼所需的物理信息及骨骼本身的信息),下面是选择了thumb_01_l——thumb_03_l 这条链。
// AnimNode_AnimDynamics.cpp line:673
void FAnimNode_AnimDynamics::InitPhysics(FComponentSpacePoseContext& Output)
{
...
for (FAnimPhysBodyDefinition& PhysicsBodyDef : PhysicsBodyDefinitions)
{
TArray<FAnimPhysShape> BodyShapes;
BodyShapes.Add(FAnimPhysShape::MakeBox(PhysicsBodyDef.BoxExtents));
...
}
...
}
③群魔乱舞(这是错误示范)
④平面限制
如下所示,可以将骨骼限制在平面之下
// AnimPhysicsSolver.cpp line:766
void FAnimPhys::ConstrainPlanar
...
⑤球体限制
效果是设置一个球体,依据驱动骨骼设置了限制的活动区域,可以设置是对内碰撞还是对外碰撞,“外部”则骨骼只能在球体外进行运动,“内部”则骨骼只能在内部进行运动,下面分别举例外部和内部的例子:
1、内部如下:
可以看到,我们绑定的骨骼只能在求体内运动,一旦脱离则会被强制收束到球上
2、外部如下:
只需将上面配置中的“内部”改为“外部”即可,效果如下,此时骨骼只能在球外运动
// AnimPhysicsSolver.cpp line:785
void FAnimPhys::ConstrainSphericalInner
...
void FAnimPhys::ConstrainSphericalOuter
...
四、引用
UE5AnimDynamics文档