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

写在前面

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

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_INFLUENCES
	BoneMatrix += Input.BlendWeights.z * GetBoneMatrix(Input.BlendIndices.z);
	BoneMatrix += Input.BlendWeights.w * GetBoneMatrix(Input.BlendIndices.w);
#if GPUSKIN_USE_EXTRA_INFLUENCES
	BoneMatrix += 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

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UE4_API_Reference 是一个非常宝贵的资源,它提供了 Unreal Engine 4 引擎的全部 API 详细文档。这些离线文档对开发者来说非常重要,因为它们可以在没有网络连接的情况下提供对 UE4 引擎的完整了解和查询支持。 UE4_API_Reference 全部离线文档包括了对 UE4 的各个模块、类、函数和属性的详细描述和用法示例。开发者可以通过这些文档深入了解各个功能模块的实现原理以及如何正确使用它们。这对于开发者学习 UE4 引擎以及进行游戏开发非常有帮助。 离线文档的好处在于可以在没有网络的环境中使用。开发者不需要依赖互联网来查找关于 UE4 API 的信息,而是可以直接在本地进行搜索和查询。这不仅提高了开发效率,还可以避免由于网络问题导致的信息获取困难。 UE4_API_Reference 全部离线文档的编制工作需要花费大量的时间和精力。文档中的每一个类、函数和属性都需要进行详细描述和示例演示,以便开发者能够更好地理解和使用。这就要求文档编写人员具备广泛的知识和丰富的经验,以确保文档的完整性和准确性。 总之,UE4_API_Reference 全部离线文档对于 UE4 开发者来说是一个非常宝贵的资源。它提供了对 UE4 引擎的全面了解和查询支持,可以帮助开发者更好地学习和使用 UE4 引擎进行游戏开发。同时,它也方便了开发者在没有网络连接的环境中进行开发工作,提高了开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值