【ue4】【使用】动画融合_分层骨骼混合

基于每个骨骼的分层骨骼混合 Layered Blend Per Bone

这种混合方式可以只对某些骨骼进行混合,其他骨骼保持原姿势。

对哪些骨骼进行混合,以及混合的权重,都可以由我们自己决定。

比较典型的用法是在奔跑的时候切换武器,就可以将上半身用 站立时切换武器的动画 进行混合, 而下半身保持原奔跑姿势不变。

使用

在已有的动画蓝图里,假设我们已经有了一个最初的运动状态机 -- 走跑跳 (idle_run_jump)

下面我们要加入 AnimMix 文件进行动画混合

首先右键动画蓝图中动画图表的空白处,并输入 Layered Blend

如果是英文版本,则会出现 Layered Blend Per Bone 选项

选择红框里的选项,动画图表里会出现一个新的节点

Base Pose -- 基础的动画姿势

Blend Pose 0 -- 我们想要混合进来的姿势0

Blend Weights 0 -- 姿势0混合时所占的权重 (取值范围0~1)

添加引脚 -- 可以继续添加混合的姿势 -- 可以混合多个姿势进来

下面我们将原来的状态机连到 Base Pose 节点, 并将 AnimMix 连到 Blend Pose 0 节点。 然后再将混合后的姿势连到最终动画姿势里。

至此,还不能奏效, 因为我们并未设置从哪个骨骼上开始混合,也没有设置混合到什么程度 (权重)

所以接下来我们点击混合节点,可以看到右侧细节面板里出现了一些参数

点击 Branch Filters 后面的加号,会增加一个元素

可以看到 Branch Filters 里面有一个元素, 这个元素包含两个成员

Bone Name 表示想要混合的骨骼名称

Blend Depth 表示混合的深度,关于这个参数的设置,后面会详细说明

可以继续添加元素,以对不同的骨骼设置不同的混合深度

这里只有一个 Branch Filters, 当我们再多添加混合的姿势的时候 (即点击添加引脚), Branch Filters 也会随着它增多

每一个 Branch Filters 对应着一个 Blend Poses

关于 Blend Depth

Blend Depth 决定当前骨骼 (及其孩子骨骼) 混合到原动画的比重

当 Blend Depth 的取值为 0 到 1 之间的任何数 (包括0和1) 时, 当前的骨骼会完全取代原来的骨骼位置,完全混合进去

当 Blend Depth 的取值为 -1 时 (不建议取其他的负值), 当前的骨骼完全不会影响原来的骨骼位置,即完全不会混合

当 Blend Depth 的取值为 大于1 的任何数时, 随着这个数值的__增加__, 当前骨骼混合到原骨骼的程度逐渐__减小__

举个比较实用的例子, 如果现在我们只想融合胳膊上部,那么我们可以将整个胳膊的父骨骼的 Blend Depth 设置为 1 (或0)

然后将 胳膊下部的父骨骼的 Blend Depth 设置为 -1

这样整个胳膊的下部就不会融合进去了


原理

Blend Depth 在 ue4 中的源代码位置记录如下, 方便以后详查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//Engine\Engine\Source\Runtime\Engine\Private\Animation\AnimationRuntime.cpp 1280行

void FAnimationRuntime::CreateMaskWeights(TArray<FPerBoneBlendWeight>& BoneBlendWeights, const TArray<FInputBlendPose>& BlendFilters, const USkeleton* Skeleton)
{
if ( Skeleton )
{
const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();

const int32 NumBones = RefSkeleton.GetNum();
BoneBlendWeights.Reset(NumBones);
BoneBlendWeights.AddZeroed(NumBones);

// base mask bone
for (int32 PoseIndex=0; PoseIndex<BlendFilters.Num(); ++PoseIndex)
{
const FInputBlendPose& BlendPose = BlendFilters[PoseIndex];

for (int32 BranchIndex=0; BranchIndex<BlendPose.BranchFilters.Num(); ++BranchIndex)
{
const FBranchFilter& BranchFilter = BlendPose.BranchFilters[BranchIndex];
const int32 MaskBoneIndex = RefSkeleton.FindBoneIndex(BranchFilter.BoneName);

if (MaskBoneIndex != INDEX_NONE)
{
// how much weight increase Per depth
const float IncreaseWeightPerDepth = (BranchFilter.BlendDepth != 0) ? (1.f/((float)BranchFilter.BlendDepth)) : 1.f;

// go through skeleton bone hierarchy.
// Bones are ordered, parents before children. So we can start looking at MaskBoneIndex for children.
for (int32 BoneIndex = MaskBoneIndex; BoneIndex < NumBones; ++BoneIndex)
{
// if Depth == -1, it's not a child
const int32 Depth = RefSkeleton.GetDepthBetweenBones(BoneIndex, MaskBoneIndex);
if (Depth != -1)
{
// when you write to buffer, you'll need to match with BasePoses BoneIndex
FPerBoneBlendWeight& BoneBlendWeight = BoneBlendWeights[BoneIndex];

BoneBlendWeight.SourceIndex = PoseIndex;
const float BlendIncrease = IncreaseWeightPerDepth * (float)(Depth + 1);
BoneBlendWeight.BlendWeight = FMath::Clamp<float>(BoneBlendWeight.BlendWeight + BlendIncrease, 0.f, 1.f);
}
}
}
}
}
}
}

可以从下面这行看出为此值对权重的影响

1
const float IncreaseWeightPerDepth = (BranchFilter.BlendDepth != 0) ? (1.f/((float)BranchFilter.BlendDepth)) : 1.f;

参考

FishGibble_LayeredBlendPerBone_YouTube

UE4 动画各种混合详细解析(二)