在Directx11入门里我有篇博客是有关GPU蒙皮骨骼动画的实现,最近研究了一下UE4的骨骼动画的数据构成,写点总结一下。
Directx11教程四十二上之SkeletalAnimation(骨骼动画)的原理
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