写在前面

本文为个人学习的笔记整理,如有错误,望不吝指出。

1. Mesh数据

SkeletalMeshComponent中和骨骼模型相关的代码主要封装在USkeletalMesh类。
USkeletalMesh包含两部分数据:骨骼数据,和模型网格数据。拥有蒙皮绑定和应用动画变形网格的功能。

其中骨骼相关数据都封装在USkeleton类,其中包括了骨架数据、虚拟骨骼、插槽、重定向等逻辑,以及一个对外的FReferenceSkeleton类,存储骨骼姿势数据。

而模型网格数据及流程主要封装在USkeletalMesh类,本篇仅简述该部分。

1.1 几何数据

  • FbxSkeletalMeshImport中的FbxImporter类负责导入FBX资源,并生成FSkeletalMeshModel对象,该对象储存着模型网格的几何数据。
  • 在USkeletalMesh对象中持有几何数据的引用,但该数据不会在Runtime时使用,Runtime时使用的是由该几何数据初始化的FSkeletalMeshRenderData对象。
    TSharedPtr<FSkeletalMeshModel> ImportedModel;
    

1.2 运行时数据

在运行期间,USkeletalMesh的Mesh相关数据存储在FSkeletalMeshRenderData
RenderData的数据进一步存在FSkeletalMeshLODRenderData

LODRenderData:

  • 含有当前LOD中所有的Mesh顶点数据,例如index,position,UV,Normal,Tangent等,还有每个顶点的影响骨骼和权重。
  • 并且还有一个划分不同section的数据结构,其中存储着某个section在各个buffer里的连续分布情况

初始化RenderData

由ImportedModel初始化SkelMesh的RenderData:

调用FSkeletalMeshRenderData::Cache进行初始化,该接口负责遍历ImportedModel的LODModel(FSkeletalMeshLODModel)数据,逐一创建对应的LODRenderData对象,用LODModel数据初始化该对象,初始化结束后将LODRenderData加入RenderData的LODRender列表,供后续使用。

LODRenderData的初始化数据和流程:BuildFromLODModel

  • 根据ImportModel的LOD数据中的Section数据,初始化LODRenderData中的Section列表
  • 向StaticVertexBuffers赋值所有顶点数据:坐标,切线,UV数据
  • 向SkinWeightVertexBuffer初始化蒙皮权重
  • 如果有Cloth数据,会初始化ClothVertexBuffer
  • 赋值ActiveBoneIndices和RequiredBones
    ActiveBoneIndices:这个LOD激活的骨骼集合
    RequiredBones:渲染这个LOD时需要被更新的骨骼集合,可能包括一些不会被渲染的骨骼。这里需要保存从Root到被渲染骨骼的骨骼链,且要严格的按照骨骼顺序存储,方便合并。

1.3 创建渲染对象

FSkeletalMeshObject:渲染对象的基类,通过该对象从Game线程往渲染线程传递数据。

1. CreateRenderState_Concurrent

创建USkinnedMeshComponent组件需要的渲染线程的信息
其中最主要的功能是根据渲染设置:静态渲染、CPU蒙皮或GPU蒙皮,创建对应的子类对象:

  • FSkeletalMeshObjectStatic
  • FSkeletalMeshObjectCPUSkin
  • FSkeletalMeshObjectGPUSkin

这三个类都继承自FSkeletalMeshObject,UE4默认是GPU蒙皮
因为SkinnedMeshComp继承自ActorComponent,该接口在ActorComp初始化时被调用。

2. FSkeletalMeshObjectGPUSkin

FSkeletalMeshObjectGPUSkin::InitResources

  • 由RenderData(LODRenderData)对FSkeletalMeshObjectLOD列表进行数据初始化
  • FSkeletalMeshObjectLOD::InitResources
    ○ 初始化顶点蒙皮权重缓冲:MeshObjectWeightBuffer
    ○ 初始化顶点颜色缓冲:MeshObjectColorBuffer
    ○ 获取RenderLODData中的顶点数据,存到中间变量FVertexFactoryBuffers(纯数据对象)
    ○ 用Buffers初始化MeshObjectLOD成员变量GPUSkinVertexFactories(FVertexFactoryData对象
    ○ 如果有布料数据,则拿FVertexFactoryBuffers也初始化ClothVertexFactories

FSkeletalMeshObjectGPUSkin::Update

由游戏线程向渲染线程的队列添加渲染指令和数据

调用流程:

  1. UWorld::SendAllEndOfFrameUpdates
 for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate)for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)Comp->DoDeferredRenderUpdates_Concurrent
  1. ActorComponent::DoDeferredRenderUpdates_Concurrent
    两个标志位:bRenderTransformDirty、bRenderDynamicDataDirty
    当帧是否有更新(rotation、transform、scale改变),或者DynamicData的数据变化。
    数据有变动的话,则分别调用SendRenderTransform_Concurrent、SendRenderDynamicData_Concurrent渲染新数据

  2. USkinnedMeshComponent::SendRenderDynamicData_Concurrent
    调MeshObject->Update

  3. FSkeletalMeshObjectGPUSkin::Update
    ○ 用RenderData创建一个渲染线程使用的数据对象DynamiacData(FDynamicSkelMeshObjectDataGPUSkin
    ○ 把相关的渲染数据放到指令列表,后面由渲染线程调用。

 FSkeletalMeshObjectGPUSkin* MeshObject = this;ENQUEUE_RENDER_COMMAND(SkelMeshObjectUpdateDataCommand)([MeshObject, FrameNumberToPrepare, RevisionNumber, NewDynamicData, GPUSkinCache](FRHICommandListImmediate& RHICmdList){FScopeCycleCounter Context(MeshObject->GetStatId());MeshObject->UpdateDynamicData_RenderThread(GPUSkinCache, RHICmdList, NewDynamicData, nullptr, FrameNumberToPrepare, RevisionNumber);}

2. 渲染

2.1 渲染数据

DynamicData:FDynamicSkelMeshObjectDataGPUSkin
储存GPU顶点蒙皮需要的矩阵数据,由游戏线程创建并发送到渲染线程进行更新

 # Vertex数据//蒙皮矩阵TArray<FMatrix> ReferenceToLocal;      TArray<FMatrix> PreviousReferenceToLocal;TArray<FTransform> MeshComponentSpaceTransforms;# Morph数据TArray<FActiveMorphTarget> ActiveMorphTargets;TArray<float> MorphTargetWeights;TArray<int32> SectionIdsUseByActiveMorphTargets;int32 NumWeightedActiveMorphTargets;# Cloth数据TMap<int32, FClothSimulData> ClothingSimData;FMatrix ClothObjectLocalToWorld;float ClothBlendWeight;

1. InitDynamicSkelMeshObjectDataGPUSkin
用RenderData和SkeletalMeshComp初始化DynamicData
调用UpdateRefToLocalMatrices。

2. UpdateRefToLocalMatrices:计算蒙皮矩阵

const TArray<FBoneIndexType>* RequiredBoneSets[3] = { &LOD.ActiveBoneIndices, ExtraRequiredBoneIndices, NULL };
for (int32 RequiredBoneSetIndex = 0; RequiredBoneSets[RequiredBoneSetIndex] != NULL; RequiredBoneSetIndex++)const TArray<FBoneIndexType>& RequiredBoneIndices = *RequiredBoneSets[RequiredBoneSetIndex];for (int32 BoneIndex = 0; BoneIndex < RequiredBoneIndices.Num(); BoneIndex++)//...//更新骨骼全局姿势ReferenceToLocal[ThisBoneIndex] = ComponentTransform[ThisBoneIndex].ToMatrixWithScale();
//...
//乘上绑定姿势逆矩阵,得到最终的蒙皮矩阵
for (int32 ThisBoneIndex = 0; ThisBoneIndex < ReferenceToLocal.Num(); ++ThisBoneIndex)ReferenceToLocal[ThisBoneIndex] = (*RefBasesInvMatrix)[ThisBoneIndex] * ReferenceToLocal[ThisBoneIndex];

该函数遍历骨骼列表,从SkeletalMeshComp获取前期Animation计算完后的骨骼全局姿势数据,初始化ReferenceToLocal矩阵列表。
然后遍历ReferenceToLocal,为每个骨骼全局姿势再乘上绑定姿势逆矩阵,得到最终的蒙皮矩阵。

绑定姿势矩阵:每骨骼的全局绑定姿势矩阵,用于将顶点从某骨骼空间变换到模型空间。
绑定姿势逆矩阵:用于将顶点从模型空间变换到骨骼空间。
蒙皮矩阵的作用是负责将顶点从模型空间下的绑定姿势,变换到模型空间下的当前动画姿势。所以需要先将顶点变换到骨骼空间,再应用骨骼全局姿势矩阵。

3. USkeletalMesh::CalculateInvRefMatrices
预计算绑定姿势逆矩阵,由USkeletalMesh的PostLoad在资源初始化时调用一次。存在USkeletalMesh的RefBasesInvMatrix中。

由于绑定姿势是常量,所以只需计算一次即可。

该函数负责逐骨骼计算:

  • 获取每根骨骼T-Pose姿态下的矩阵,暂存在CachedComposedRefPoseMatrices
  • 对每根骨骼,找到其父骨骼的T-Pose矩阵,应用在子骨骼上,暂存在CachedComposedRefPoseMatrices
  • 对每根骨骼,取CachedComposedRefPoseMatrices矩阵数据的逆矩阵,存到RefBasesInvMatrix

2.2 处理渲染数据

1. 渲染线程入口
FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread

  • 获取游戏线程传递的DynamicData
  • 调ProcessUpdatedDynamicData处理数据

2. FSkeletalMeshObjectGPUSkin::ProcessUpdatedDynamicData

  • DynamicData.LODIndex索引MeshObjectLOD和存在SkeletalMeshComp中的Render数据(LODRenderData和RenderSection列表)
  • MeshObjectLOD获取顶点数据:FVertexFactoryData
  • 遍历RenderSection,用SectionIdx索引,顶点数据中获取对应的顶点工厂对象(FGPUBaseSkinVertexFactory),调用其UpdateBoneData接口将骨骼变换矩阵数据存入到 GBoneUniformStruct,再写入UniformBuffer,后续在 shader 中使用。
for (int32 SectionIdx = 0; SectionIdx < Sections.Num(); SectionIdx++)
{const FSkelMeshRenderSection& Section = Sections[SectionIdx];...FGPUBaseSkinVertexFactory* VertexFactory;{VertexFactory = VertexFactoryData.VertexFactories[SectionIdx].Get();}
...
TArray<FMatrix>& ReferenceToLocalMatrices = DynamicData->ReferenceToLocal;
bool bNeedFence = ShaderData.UpdateBoneData(RHICmdList, ReferenceToLocalMatrices, Section.BoneMap, RevisionNumber, false, FeatureLevel, bUseSkinCache);

3. 顶点工厂
FGPUBaseSkinVertexFactory

FGPUBaseSkinVertexFactory::FShaderDataType

4. Shader

FGPUBaseSkinVertexFactory定义的宏:

IMPLEMENT_GPUSKINNING_VERTEX_FACTORY_TYPE:将GPU蒙皮的VertexFactory绑定到对应的Shader(GpuSkinVertexFactory.ush)

#define IMPLEMENT_GPUSKINNING_VERTEX_FACTORY_TYPE(FactoryClass, ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)IMPLEMENT_GPUSKINNING_VERTEX_FACTORY_TYPE(TGPUSkinVertexFactory, "/Engine/Private/GpuSkinVertexFactory.ush", true, false, true, false, false);

2.3 GpuSkinVertexFactory.ush

负责GPU蒙皮的顶点shader

ush是UE4中Shader的头文件,usf是Shader的源文件

1.FVertexFactoryInput:

C++(CPU)的输入数据,封装顶点坐标(Position)、切线(Tangent)、影响蒙皮的骨骼索引和权重(BlendIndices、blendWeights),以及其他渲染需要的数据,如纹理。

2.GetVertexFactoryIntermediates

返回值FVertexFactoryIntermediates

Intermediates.UnpackedPosition = UnpackedPosition(Input);
Intermediates.BlendMatrix = CalcBoneMatrix( Input );
Intermediates.TangentToLocal = SkinTangents(Input, Intermediates);
Intermediates.Color = Input.Color FCOLOR_COMPONENT_SWIZZLE;

该函数根据参数顶点蒙皮的若干骨骼,及其骨骼变换矩阵,计算蒙皮矩阵。以及计算切线矩阵。

蒙皮矩阵的计算在CalcBoneMatrix

 FBoneMatrix BoneMatrix = Input.BlendWeights.x * GetBoneMatrix(Input.BlendIndices.x);BoneMatrix += Input.BlendWeights.y * GetBoneMatrix(Input.BlendIndices.y);
#if !GPUSKIN_LIMIT_2BONE_INFLUENCESBoneMatrix += Input.BlendWeights.z * GetBoneMatrix(Input.BlendIndices.z);BoneMatrix += Input.BlendWeights.w * GetBoneMatrix(Input.BlendIndices.w);
#if GPUSKIN_USE_EXTRA_INFLUENCESBoneMatrix += Input.BlendWeightsExtra.x * GetBoneMatrix(Input.BlendIndicesExtra.x);BoneMatrix += Input.BlendWeightsExtra.y * GetBoneMatrix(Input.BlendIndicesExtra.y);BoneMatrix += Input.BlendWeightsExtra.z * GetBoneMatrix(Input.BlendIndicesExtra.z);BoneMatrix += Input.BlendWeightsExtra.w * GetBoneMatrix(Input.BlendIndicesExtra.w);

支持4骨骼或8骨骼蒙皮
线性蒙皮,将所有对该顶点有影响的骨骼变换矩阵,乘上权重再累加起来,得到一个完整的蒙皮矩阵。

2.4 FPrimitiveSceneProxy

UE4源码阅读_骨骼模型与动画系统_Mesh相关推荐

  1. UE4源码阅读_骨骼模型与动画系统_动画流程

    0. 写在前面 本文为个人学习的笔记整理,如有错误,望不吝指出. 本篇粗略的描述UE4中每帧动画计算的主流程,该流程涉及的相关代码,因为动画设置的多样性,还有相当多的分支代码. 本篇只描述角色跑起一个 ...

  2. java经典源码 阅读_公开!阿里甩出“源码阅读指南”,原来源码才是最经典的学习范例...

    我们为啥要阅读源码? 为什么面试要问源码?为什么我们Java程序员要去看源码?相信大多数程序员看到源码第一感觉都是:枯燥无味,费力不讨好!要不是为了"涨薪"我才不去看这个鬼东西!但 ...

  3. UE4 源码阅读:从引擎启动到Receive Begin Play

    一.引擎主循环 UE版本:4.27 一.引擎主循环的位置: Launch.cpp : Guarded Main函数 二..Guarded Main函数执行逻辑: 1.EnginePreInit:加载大 ...

  4. 【游戏编程扯淡精粹】EASTL源码阅读

    [游戏编程扯淡精粹]EASTL源码阅读 侯捷先生在<漫谈程序员与编程> 中讲到 STL 运用的三个档次:"会用 STL,是一种档次.对 STL 原理有所了解,又是一个档次.追踪过 ...

  5. android tcp socket框架_最流行的 Web 框架 Gin 源码阅读

    最近公司大部分项目开始往golang换, api的框架选定使用gin, 于是将 gin的源码看了一遍, 会用几篇文章将gin的流程及流程做一个梳理, 下面进入正题. gin框架预览 上图大概是 gin ...

  6. bert模型简介、transformers中bert模型源码阅读、分类任务实战和难点总结

    bert模型简介.transformers中bert模型源码阅读.分类任务实战和难点总结:https://blog.csdn.net/HUSTHY/article/details/105882989 ...

  7. 源码 状态机_[源码阅读] 阿里SOFA服务注册中心MetaServer(1)

    [源码阅读] 阿里SOFA服务注册中心MetaServer(1) 0x00 摘要 0x01 服务注册中心 1.1 服务注册中心简介 1.2 SOFARegistry 总体架构 1.3 为什么要分层 0 ...

  8. 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型

    1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...

  9. 基于lis3dh的简易倾角仪c源码_开源网关apisix源码阅读和最佳实践

    大家应该都接手过这种项目,前人找一个开源软件改一改,发上线. 我这里便曾经遇到过类似的问题. 随着需求的增加,各种维护人员东改改西改改,原来的开源项目被改的面目全非,再也无法和上游合并. 甚至TLS协 ...

最新文章

  1. 为什么人工智能被过度炒作?
  2. python pkl是什么类型的文件?怎么来打开它?(使用numpy和pickle都能打开)
  3. StackExchange.Redis 使用-配置
  4. c语言编译生成cpp,C语言的编译过程
  5. jQuery对象和DOM对象的区别和转换
  6. CSS中可以通过哪些属性定义,使得一个DOM元素不显示在浏览器可视范围内?
  7. phpstudy运行PHP项目出现404怎么办?
  8. 高中理科不好学计算机,高一理科很差但想学理咋办
  9. w ndows7浏览器网页,win7系统IE浏览器播放网页视频失败的解决方法
  10. 自定义 C++ 中的 range() 函数
  11. java谁对view进行渲染_为啥 Response.Write 后,View就不渲染了?
  12. Rust : 泛型与trait限定
  13. java vector编程_java中Vector实现方法和功能还有例子详细讲解一下!谢谢!
  14. 小程序菜鸟的父子组件传值、父子组件方法调用的学习
  15. matlab tiff 压缩方式,无法打开以这种方式压缩的tiff文件(2) - imageJ / FIJI
  16. oracle ogg操作日志,对一段Oracle GoldenGate (OGG) 传输过程日志(.rpt文件)的解释...
  17. 达梦工具导出慢sqle的xcel表格方法
  18. 区块链DApp从零开始学 (二) | 超详细 DApp创建 | 发行代币token | 宠物领养
  19. LInux:进程等待之wait() waitpid()
  20. UVa 10115 - Automatic Editing

热门文章

  1. 【cs224n学习作业】Assignment 1 - Exploring Word Vectors
  2. VIEWGOOD(远古)供应校园网视频点播系统
  3. 数据分析——用户粘性指标 DAU/MAU
  4. pcs增加mysql资源_PCS命令配置corosyncpacemaker群集操作步骤.doc
  5. MySQL备份恢复-mysqldump/xbk
  6. 余世维成功经理人讲座
  7. 如何用Python识别图片中的文字?
  8. MeSH 医学主题词数据库
  9. 客户端和服务器的关系
  10. # 科研牛人告诉研究生怎么看文献,怎么写论文csdn