(UE4 4.21 ) UE4获取SkeletalMesh顶点的骨骼权重(BoneWeight),骨骼索引(BoneIndex),骨骼变换矩阵(BoneMatrix)

本文深入探讨了UE4中骨骼动画的实现原理,包括骨骼权重、骨骼索引的存储方式,以及如何通过BoneMap获取真实骨骼索引。此外,还详细讲解了骨骼树的结构及其在3D图形学中的应用,以及骨骼变换矩阵的计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Directx11入门里我有篇博客是有关GPU蒙皮骨骼动画的实现,最近研究了一下UE4的骨骼动画的数据构成,写点总结一下。

Directx11教程四十二上之SkeletalAnimation(骨骼动画)的原理

Directx11教程四十二中之3D动画格式M3D

Directx11教程四十二下之进行骨骼动画的渲染

UE4的骨骼动画相关类UML图

 

UE4获取骨骼权重(BoneWeight),骨骼索引(BoneIndex)

顶点的权重和索引存储在FSoftSkinVertex结构体里, 这里得注意的是每个SkeletalMesh存在多个Lod等级,每个Lod等级下的三角形面数,顶点数是不一样的。



	if (NULL == skeletalMesh)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMesh is NULL"));
		return;
	}

	FSkeletalMeshModel* skeletalMeshModel = skeletalMesh->GetImportedModel();
	if (NULL == skeletalMeshModel)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMeshModel is NULL"));
		return;
	}

	USkeleton* skeleton = skeletalMesh->Skeleton;
	if (NULL == skeleton)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeleton is NULL"));
		return;
	}

	int lodNum = skeletalMeshModel->LODModels.Num();
	for (int index = 0; index < lodNum; ++index)
	{
		const FSkeletalMeshLODModel& skeMeshLodMesh = skeletalMeshModel->LODModels[index];
		TArray<FSoftSkinVertex> arrayVertices;
		skeMeshLodMesh.GetVertices(arrayVertices);
	}

上面获取的是TArray<FSoftSkinVertex>是“各个FSkelMeshSection部分的TArray<FSoftSkinVertex>”组成的。


void FSkeletalMeshLODModel::GetVertices(TArray<FSoftSkinVertex>& Vertices) const
{
	Vertices.Empty(NumVertices);
	Vertices.AddUninitialized(NumVertices);

	// Initialize the vertex data
	// All chunks are combined into one (rigid first, soft next)
	FSoftSkinVertex* DestVertex = (FSoftSkinVertex*)Vertices.GetData();
	for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); SectionIndex++)
	{
		const FSkelMeshSection& Section = Sections[SectionIndex];
		FMemory::Memcpy(DestVertex, Section.SoftVertices.GetData(), Section.SoftVertices.Num() * sizeof(FSoftSkinVertex));
		DestVertex += Section.SoftVertices.Num();
	}
}

 

用官方的机器人网格骨骼读取的数据如下:

 

 

(1)SkeletalMesh的每个顶点有8组uv,8组骨骼权重,8组虚拟骨骼索引

(2)骨骼权重用uint8整数表示,最大骨骼权重值为255, 所有骨骼的权重加起来等于255,想算出真正的骨骼权重只需要除以255

 

上面我说FSoftSkinVertex存储的骨骼索引是“虚拟骨骼索引”,因为这也并非是真正的骨骼索引,想得到真正的骨骼索引得在经历一次转化,原因往下面看。

UE4的骨骼树

在之前DX11 骨骼动画相关博客已经讲述过骨骼树的原理:Directx11教程四十二上之SkeletalAnimation(骨骼动画)的原理,差不多两年前的东西了。

骨骼树是一个树的数据结构,在3D图形学经常是用数据索引来表示的树(每个节点存储母节点的数组索引),root是根骨骼,其母骨骼索引为-1,表示已经到了根部。

上面我们说到 FSoftSkinVertex 存储的为虚拟骨骼索引,是因为其并非真正的骨骼索引,想获得真正的骨骼索引得经过BoneMap的映射.

	if (NULL == skeletalMesh)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMesh is NULL"));
		return;
	}

	FSkeletalMeshModel* skeletalMeshModel = skeletalMesh->GetImportedModel();
	if (NULL == skeletalMeshModel)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMeshModel is NULL"));
		return;
	}

	USkeleton* skeleton = skeletalMesh->Skeleton;
	if (NULL == skeleton)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeleton is NULL"));
		return;
	}

	const TArray<FMeshBoneInfo>& arrayMeshBoneInfo = skeleton->GetReferenceSkeleton().GetRefBoneInfo();
	
	int lodNum = skeletalMeshModel->LODModels.Num();
	for (int lodIndex = 0; lodIndex < lodNum; ++lodIndex)
	{
		const FSkeletalMeshLODModel& skeMeshLodMesh = skeletalMeshModel->LODModels[lodIndex];

		for (int sectionIndex = 0; sectionIndex < skeMeshLodMesh.Sections.Num(); ++sectionIndex)
		{
			const FSkelMeshSection& skelMeshSection = skeMeshLodMesh.Sections[sectionIndex];
			for (int vertexIndex = 0; vertexIndex < skelMeshSection.SoftVertices.Num(); ++vertexIndex)
			{
				int vertexVirtualBoneIndex = skelMeshSection.SoftVertices[vertexIndex].InfluenceBones[0];
				int vertexBoneIndex = skelMeshSection.BoneMap[vertexVirtualBoneIndex];
			}
		}	
	}

骨骼树的信息

const TArray<FMeshBoneInfo>& arrayMeshBoneInfo = skeleton->GetReferenceSkeleton().GetRefBoneInfo();

BoneMap

 

FSoftSkinVertex的InfluenceBones是虚拟骨骼索引,得在经历BoneMap的映射才得到真正的骨骼索引

int vertexVirtualBoneIndex = skelMeshSection.SoftVertices[vertexIndex].InfluenceBones[0];
int vertexBoneIndex = skelMeshSection.BoneMap[vertexVirtualBoneIndex];

 

骨骼变换矩阵(BoneMatrix)

BoneMatrix: 某个SkeletalMesh的某个AnimSequence的某个时间点下的某根骨骼的变换矩阵

计算BoneMatrix方法和之前文章Directx11教程四十二上之SkeletalAnimation(骨骼动画)的原理,大致是顶点得从localSpace变换到骨骼的绑定T-Pose空间(默认的骨骼状态),在从当前骨骼的矩阵一直乘以母节点的矩阵一直到根节点,最后得到boneMatrix.

 

求某个SkeletalMesh的某个AnimSequence的某个时间点下的某根骨骼在其骨骼空间的变换矩阵(BoneSpaceMatrix)

FTransform GetBoneTransform(USkeletalMesh* skeletalMesh, UAnimSequence* animSequence, float time, FName boneName)
{
	FTransform boneTransform = FTransform::Identity;
	int boneIndex = -1;
	boneIndex = skeletalMesh->RefSkeleton.FindBoneIndex(boneName);
	if (-1 != boneIndex)
	{
		int trackIndex = skeletalMesh->Skeleton->GetAnimationTrackIndex(boneIndex, animSequence, true);

		if (-1 != trackIndex)
		{
			animSequence->GetBoneTransform(boneTransform, trackIndex, time, true);
		}
	}


	return boneTransform;
}

最终求出所有骨骼的BoneMatrix

 

    UPROPERTY(EditAnywhere)
	USkeletalMesh* skeletalMesh;

    UPROPERTY(EditAnywhere)
	UAnimSequence* animSequence;

    struct FBoneData
    {
	int parentIndex = -1;
	FName boneName;
    };

    TArray<FBoneData> arrayBoneData;

	if (NULL == skeletalMesh)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMesh is NULL"));
		return;
	}

	FSkeletalMeshModel* skeletalMeshModel = skeletalMesh->GetImportedModel();
	if (NULL == skeletalMeshModel)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeletalMeshModel is NULL"));
		return;
	}

	USkeleton* skeleton = skeletalMesh->Skeleton;
	if (NULL == skeleton)
	{
		UE_LOG(LogMyTest, Warning, TEXT("skeleton is NULL"));
		return;
	}

	const TArray<FMeshBoneInfo>& arrayMeshBoneInfo = skeleton->GetReferenceSkeleton().GetRefBoneInfo();

        //每根骨骼都有一个baseInvMatrix,负责将骨骼从localSpace变换到其绑定T-Pose时的骨骼空间,可以理解为是将骨骼从其BoneSpace变换到localSpace的逆矩阵
	const TArray<FMatrix>& baseInvMatrix = skeletalMesh->RefBasesInvMatrix;

	if (baseInvMatrix.Num() != arrayMeshBoneInfo.Num())
	{
		UE_LOG(LogMyTest, Warning, TEXT("baseInvMatrix.Num() != arrayBoneData.Num()"));
		return;
	}

	for (int boneIndex = 0; boneIndex < arrayMeshBoneInfo.Num(); ++boneIndex)
	{
		const FMeshBoneInfo& meshBoneInfo = arrayMeshBoneInfo[boneIndex];
		arrayBoneData.Add(FBoneData{ meshBoneInfo.ParentIndex, meshBoneInfo.Name });
	}

	TMap<FName, FMatrix> mapBoneMatrix;

	const float testTime = 0.03f;
	
	for (int boneIndex = 0; boneIndex < arrayBoneData.Num(); ++boneIndex)
	{
		FMatrix boneMatrix = baseInvMatrix[boneIndex];
		int parentIndex = boneIndex;
		while (-1 != parentIndex)
		{
			FTransform boneTransform = GetBoneTransform(skeletalMesh, animSequence, testTime, arrayBoneData[parentIndex].boneName);
			boneMatrix *= boneTransform.ToMatrixWithScale();
			parentIndex = arrayBoneData[parentIndex].parentIndex;
		}

		mapBoneMatrix.Add(arrayBoneData[boneIndex].boneName, boneMatrix);
	}

 

参考资料

[1].SkeletalMesh.cpp的GetRefVertexLocationTyped函数

[2].https://answers.unrealengine.com/questions/349862/bone-transform-at-certain-timekey.html

[3].Directx11教程四十二上之SkeletalAnimation(骨骼动画)的原理

[4].Directx11教程四十二中之3D动画格式M3D

[5].Directx11教程四十二下之进行骨骼动画的渲染
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值