Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhiruiLi committed Aug 16, 2022
1 parent da300b5 commit 304c179
Showing 1 changed file with 9 additions and 12 deletions.
21 changes: 9 additions & 12 deletions posts/unreal-pose-assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,21 @@
<a href=/tags/animation/>#Animation</a>&nbsp;</span><div class=post-content><h2 id=概述>概述
<a href=#%e6%a6%82%e8%bf%b0 class=h-anchor aria-hidden=true>#</a></h2><p>PoseAsset 是 UE 提供的一种基于曲线驱动动画的方式 <sup id=fnref:1><a href=#fn:1 class=footnote-ref role=doc-noteref>1</a></sup>。传统动画使用关键帧来控制,关键帧之间的状态计算使用前后关键帧状态插值来实现,而 PoseAsset 则是通过定义动画的极值,然后对这些极值进行加权来组合出动画。比如先定义眼睛睁开到最大和闭合的状态,然后,通过曲线控制它们的权重,实现眨眼的效果。一个常见的应用场景就是基于 FACS <sup id=fnref:2><a href=#fn:2 class=footnote-ref role=doc-noteref>2</a></sup> 实现面部表情。而这个能力也非常适合用于进行游戏中常见的捏人操作,在本文中我们将会讨论如何在 UE 中使用 PoseAsset 实现捏人功能,并进一步优化工作流。</p><h2 id=基础使用>基础使用
<a href=#%e5%9f%ba%e7%a1%80%e4%bd%bf%e7%94%a8 class=h-anchor aria-hidden=true>#</a></h2><p>在艺术家制作好 PoseAsset 资源后,将其以动画骨骼动画的形式导出为 FBX 文件,然后导入 UE 之中,打开这个动画资产,我们大致能看到这样无意义的动画:</p><p><img src=/images/ue_pose_assets_raw_anim.gif alt></p><p>一般来说,这里动画的第一帧是参考体型,之后每一帧都有具体的含义,代表某一个捏人参数的极值,因此我们可以看到上图中模型各个部位会逐个发生形状变化。在导入了这个动画后,右键点击该动画资产,选择「Create」-「Create PoseAsset」创建一个 PoseAsset:</p><p><img src=/images/ue_pose_assets_convert_menu.png alt></p><p>此时 UE 会弹出创建 PoseAsset 的提示,窗口上面选择使用的动画,下面则填入这个动画序列每一帧代表的含义。如果不填的话,引擎就只能给它默认的无意义的名字,不直观也不方便后续操作。一般来说这个名字列表需要由 PoseAsset 资源的制作者提供:</p><p><img src=/images/ue_pose_assets_convert_window.png alt></p><p>创建好 PoseAsset 后,一般我们还会将其模式改为叠加模式,以方便它和其他动画组合使用。双击这个资产打开编辑窗口,找到「Asset Details」面板,将这里的「Additive」选项勾上,然后点「Convert to Additive Pose」按钮即可:</p><p><img src=/images/ue_pose_assets_set_additive.png alt></p><p>在这个资产编辑窗口中我们可以看到刚刚填入的每一帧对应的名字,此时可以随意调整其数值预览它产生的效果,例如下图中将「upperLegStrong_L」的值改到 1,就看到模型左腿上部变粗了:</p><p><img src=/images/ue_pose_assets_curve_preview.png alt></p><p>这些 pose 的名字也绑定于对应的动画曲线名,在「Anim Curves」面板中我们可以看到这些曲线。显然,由于动画曲线和骨架绑定,因此 pose 名需要在这个骨架内保证唯一:</p><p><img src=/images/ue_pose_assets_anim_curve.png alt></p><p>使用 PoseAsset 很简单,只需要在动画蓝图的输入节点和输出节点之间插入「Modify Curve」节点 <sup id=fnref:3><a href=#fn:3 class=footnote-ref role=doc-noteref>3</a></sup> 和对应的 PoseAsset 节点即可,如下图所示:</p><p><img src=/images/ue_pose_assets_anim_bp_nodes.png alt></p><p>其中,「Modify Curve」节点就相当于刚刚在编辑窗口中手动调节每个曲线的值,这里的曲线值可以通过自行定义对应的动画蓝图变量来赋值:</p><p><img src=/images/ue_pose_assets_anim_bp_variables.png alt></p><p>接下来我们新建一个蓝图类,在上面挂载「USkeletalMeshComponent」,并使用前面的动画蓝图和对应的模型,并将「Animation Class」设定为前面的动画蓝图:</p><p><img src=/images/ue_pose_assets_bp_mesh.png alt></p><p>为了暴露动画蓝图的参数给游戏侧控制,我们还要在该蓝图中定义一组和动画蓝图中一一对应的变量:</p><p><img src=/images/ue_pose_assets_anim_bp_variables.png alt></p><p>回到动画蓝图的编辑界面,打开它的「Event Graph」,在其中对每一个动画蓝图中的变量进行赋值,变量值的来源就是刚刚在蓝图中定义的对应变量:</p><p><img src=/images/ue_pose_assets_anim_bp_assign.png alt></p><p>此时,我们就可以在场景中通过蓝图里的变量控制曲线值,进而进行捏人了:</p><p><img src=/images/ue_pose_assets_modify_curves.gif alt></p><h2 id=使用优化>使用优化
<a href=#%e4%bd%bf%e7%94%a8%e4%bc%98%e5%8c%96 class=h-anchor aria-hidden=true>#</a></h2><p>UE 自带的 PoseAsset 能力足够实现捏人的能力,但在实际应用的过程中还是不够方便。从上面的案例中也能看出,我们需要在蓝图和动画蓝图中定义一堆对应的变量,而且还需要手动连接非常多的引脚,这不仅麻烦而且没法配置化,我们在实际应用的时候一般希望能通过一个配置文件指定有哪些曲线可以编辑,然后在代码中按名字修改其数据,而不是在蓝图中连接一堆引脚。</p><p>为了实现这个功能,我们需要实现一个批量修改曲线的 anim node,这一块的文档不多,比较简单的方式是参考 UE 自己的实现,既然我们也是要修改曲线,那直接参考 UE 的 <code>FAnimNode_ModifyCurve</code> 类型 <sup id=fnref:4><a href=#fn:4 class=footnote-ref role=doc-noteref>4</a></sup> 即可,大致声明如下:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cpp data-lang=cpp><span style=display:flex><span><span style=color:#75715e>// Pose 曲线的修改数据。
</span></span></span><span style=display:flex><span><span style=color:#75715e></span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>FCurveModifyData</span> {
</span></span><span style=display:flex><span> FName Name; <span style=color:#75715e>// Pose 名
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> <span style=color:#66d9ef>float</span> Value; <span style=color:#75715e>// Pose 值
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>};
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#75715e>// 用于批量修改一组 pose 曲线的 anim node。
<a href=#%e4%bd%bf%e7%94%a8%e4%bc%98%e5%8c%96 class=h-anchor aria-hidden=true>#</a></h2><p>UE 自带的 PoseAsset 能力足够实现捏人的能力,但在实际应用的过程中还是不够方便。从上面的案例中也能看出,我们需要在蓝图和动画蓝图中定义一堆对应的变量,而且还需要手动连接非常多的引脚,这不仅麻烦而且没法配置化,我们在实际应用的时候一般希望能通过一个配置文件指定有哪些曲线可以编辑,然后在代码中按名字修改其数据,而不是在蓝图中连接一堆引脚。</p><p>为了实现这个功能,我们需要实现一个批量修改曲线的 anim node,这一块的文档不多,比较简单的方式是参考 UE 自己的实现,既然我们也是要修改曲线,那直接参考 UE 的 <code>FAnimNode_ModifyCurve</code> 类型 <sup id=fnref:4><a href=#fn:4 class=footnote-ref role=doc-noteref>4</a></sup> 即可,大致声明如下:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cpp data-lang=cpp><span style=display:flex><span><span style=color:#75715e>// 用于批量修改一组 pose 曲线的 anim node。
</span></span></span><span style=display:flex><span><span style=color:#75715e></span>USTRUCT(BlueprintInternalUseOnly)
</span></span><span style=display:flex><span><span style=color:#66d9ef>struct</span> <span style=color:#a6e22e>MY_API</span> FAnimNode_BatchModifyPoseCurve : <span style=color:#66d9ef>public</span> FAnimNode_Base {
</span></span><span style=display:flex><span> GENERATED_USTRUCT_BODY()
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> UPROPERTY(EditAnywhere, EditFixedSize, BlueprintReadWrite, Category <span style=color:#f92672>=</span> Links)
</span></span><span style=display:flex><span> FPoseLink SourcePose;
</span></span><span style=display:flex><span> <span style=color:#75715e>/** ... **/</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#66d9ef>virtual</span> <span style=color:#66d9ef>void</span> <span style=color:#a6e22e>Evaluate_AnyThread</span>(FPoseContext<span style=color:#f92672>&amp;</span> Output) <span style=color:#66d9ef>override</span>;
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span> <span style=color:#75715e>/** ... **/</span>
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>private</span><span style=color:#f92672>:</span>
</span></span><span style=display:flex><span> TArray<span style=color:#f92672>&lt;</span>FCurveModifyData<span style=color:#f92672>&gt;</span> CurveModifyData;
</span></span><span style=display:flex><span> <span style=color:#75715e>// FNamedCurveValue 定义在 Animation/CurveSourceInterface.h 中
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> TArray<span style=color:#f92672>&lt;</span>FNamedCurveValue<span style=color:#f92672>&gt;</span> CurveModifyData;
</span></span><span style=display:flex><span>};
</span></span></code></pre></div><p>其中,主要逻辑实现在 <code>Evaluate_AnyThread</code> 中,同样,设置曲线值的逻辑也可以参考 <code>FAnimNode_ModifyCurve::Evaluate_AnyThread</code> 的实现,大致如下:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-cpp data-lang=cpp><span style=display:flex><span>USkeleton <span style=color:#66d9ef>const</span><span style=color:#f92672>*</span> Skeleton <span style=color:#f92672>=</span> Output.AnimInstanceProxy<span style=color:#f92672>-&gt;</span>GetSkeleton();
</span></span><span style=display:flex><span><span style=color:#66d9ef>for</span> (<span style=color:#66d9ef>auto</span> <span style=color:#66d9ef>const</span><span style=color:#f92672>&amp;</span> ModifyItem : CurveModifyData) {
Expand All @@ -39,14 +36,14 @@
</span></span><span style=display:flex><span><span style=color:#66d9ef>public</span><span style=color:#f92672>:</span>
</span></span><span style=display:flex><span> <span style=color:#75715e>// 设置 pose 曲线数据。
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> UFUNCTION(BlueprintCallable)
</span></span><span style=display:flex><span> <span style=color:#66d9ef>void</span> SetPoseCurves(TArray<span style=color:#f92672>&lt;</span>FCurveModifyData<span style=color:#f92672>&gt;</span> Data);
</span></span><span style=display:flex><span> <span style=color:#66d9ef>void</span> SetPoseCurves(TArray<span style=color:#f92672>&lt;</span>FNamedCurveValue<span style=color:#f92672>&gt;</span> Data);
</span></span><span style=display:flex><span> <span style=color:#75715e>// 数据是否被修改过。
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> <span style=color:#66d9ef>bool</span> <span style=color:#a6e22e>IsDirty</span>() <span style=color:#66d9ef>const</span>;
</span></span><span style=display:flex><span> <span style=color:#75715e>// 获取 pose 曲线数据。
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> TArray<span style=color:#f92672>&lt;</span>FCurveModifyData<span style=color:#f92672>&gt;</span> GetPoseCurves();
</span></span></span><span style=display:flex><span><span style=color:#75715e></span> TArray<span style=color:#f92672>&lt;</span>FNamedCurveValue<span style=color:#f92672>&gt;</span> GetPoseCurves();
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>private</span><span style=color:#f92672>:</span>
</span></span><span style=display:flex><span> TArray<span style=color:#f92672>&lt;</span>FCurveModifyData<span style=color:#f92672>&gt;</span> PoseCurves;
</span></span><span style=display:flex><span> TArray<span style=color:#f92672>&lt;</span>FNamedCurveValue<span style=color:#f92672>&gt;</span> PoseCurves;
</span></span><span style=display:flex><span> <span style=color:#66d9ef>bool</span> bDirty <span style=color:#f92672>=</span> false;
</span></span><span style=display:flex><span> <span style=color:#66d9ef>mutable</span> FCriticalSection CriticalSection;
</span></span><span style=display:flex><span>};
Expand Down

0 comments on commit 304c179

Please sign in to comment.