文章目錄
- 一、引言
- 二、骨骼控制
- 三、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文檔