jquave 发表于 2021-3-24 18:58

Unreal动画混合权重(Weight)的原理分析

Unreal动画混合权重(Weight)的原理分析


BlendSpace是unreal中动画二维混合工具,常见的应用有:主角战斗视角下速度和方向的locomotion动画混合,竖直方向和水平方向的lookat动画混合。

本文主要分析BlendSpace的实现原理,并不是使用教程。
编辑器Editor部分

基本原理


融合图中,所有白色的菱形点,都是策划拖动进来的动画(AnimSequence),这个在unreal引擎中叫BlendSamples,下文统一叫做采样点。

编辑器首先会对这些采样点从0开始进行编号,引擎后续对采样点的引用都用编号来表示;然后,会对这些采样点进行三角剖分,从而得到一系列的三角形。

在编辑器里,策划已经配置好了格子的行数和列数;编辑器会遍历所有格子点(GridElement),检查当前格子点所在的三角形,根据三角形的三个顶点,计算各自权重,融合得到格子点的动画信息。一个格子点的动画信息,就是三个采样点的编号和权重。
源码分析


编辑器的入口函数在:SAnimationBlendSpace.cpp SBlendSpaceEditor::ResampleData(),此函数的基本原理和我上述的描述类似,不再赘述。

下面重点介绍融合机制:就是如何从一个三角形的三个采样点,获取其中一个格子点的动画融合权重?

核心融合代码的调用关系为:
SBlendSpaceEditor::ResampleDataSAnimationBlendSpace.cppFBlendSpaceGrid::GenerateGridElementsAnimationBlendSpaceHelpers.cppFBlendSpaceGrid::FindTriangleThisPointBelongsToAnimationBlendSpaceHelpers.cppFMath::GetBaryCentric2DUnrealMath.cpp

<pre class="brush:cpp;" style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">FVector FMath::GetBaryCentric2D(const FVector& Point, const FVector& A, const FVector& B, const FVector& C)
{
float a = ((B.Y-C.Y)(Point.X-C.X) + (C.X-B.X)(Point.Y-C.Y)) / ((B.Y-C.Y)(A.X-C.X) + (C.X-B.X)(A.Y-C.Y));
float b = ((C.Y-A.Y)(Point.X-C.X) + (A.X-C.X)(Point.Y-C.Y)) / ((B.Y-C.Y)(A.X-C.X) + (C.X-B.X)(A.Y-C.Y));
return FVector(a, b, 1.0f - a - b);
}</pre>

这里的数学知识可以温习下参考文献2

假设三角形为ABC,任意一格子点为P,A点对P的融合权重a == 三角形PBC的面积 / 三角形ABC的面积。

三角形PBC的面积 = PC X BC = (P.x - C.x) (B.y - C.y) - (B.x - C.x) * (P.y - C.y)

三角形ABC的面积 = AC X BC = (A.x - C.x) (B.y - C.y) - (B.x - C.x) * (A.y - C.y)

X表示向量叉乘运算,上面的简单推导证明了函数GetBaryCentric2D的正确性。
Runtime部分

基本原理


根据运行时输入的两个参数BlendInput,从而确定格子坐标GridIndex,找到所在格子,一个格子对应有四个格子点LeftTop、RightTop、LeftBottom、RightBottom。

引擎将每个格子,都Normalize为1 * 1的正方形,保证每个格子的总面积是 1。

BlendInput的点和LeftTop、RightTop、LeftBottom、RightBottom分别组成一个长方形,每个长方形的面积就是对应格子点的最终融合权重,基本原理和上面三角形融合类似。
源码分析


源码入口UBlendSpaceBase::TickAssetPlayer BlendSpaceBase.cpp

融合代码的调用堆栈:
UBlendSpaceBase::GetSamplesFromBlendInput BlendSpaceBase.cppUBlendSpace::GetRawSamplesFromBlendInput BlendSpace.cppUBlendSpace::GetGridSamplesFromBlendInput BlendSpace.cpp
Unreal的BlendSpace混合速率(timeScale)的原理分析


可以注意到,BlendSpace的每个Sample都有一个RateScale属性,表示当前Sample的播放速率。

BlendSpace允许出现两个Animation相同的Sample,而且这两个Sample的RateScale可以不同。

BlendSpace的最终播放速率的计算都在:Runtime阶段。
合并相同Animation的Sample


BlendSpace会把属性Animation相同的Samples,合并为一个Sample。

假设:Animation相同的Samples集合设为SameSamples,集合中第i个Sample用SameSamples表示,合并后的Sample设为MergedSample。

MergedSample.RateScale的计算,可以按如下方式理解:
先对SameSamples的Weights进行归一化(保证总和是1),设为NormalWeightsMergedSample.RateScale = Σ SameSamples.RateScale * NormalWeights

MergedSample.Weight == Σ SameSamples.Weight

这块的代码在:UBlendSpaceBase::GetSamplesFromBlendInput BlendSpaceBase.cpp
按照Weight最高的Sample的RateScale进行播放


这块的源码直接看:UBlendSpaceBase::TickAssetPlayer BlendSpaceBase.cpp
参考文档:


https://api.unrealengine.com/INT/API/Runtime/Core/Math/FMath/GetBaryCentric2D/index.html

http://mathworld.wolfram.com/BarycentricCoordinates.html

https://docs-origin.unrealengine.com/latest/CHN/Engine/Animation/Blendspaces/index.html
页: [1]
查看完整版本: Unreal动画混合权重(Weight)的原理分析