概要
Unreal Engine(UE)でのスキニング情報がどう扱われているのかを知ることでスキニングとUEへの理解を深めましょう。
この記事はUE4.19のソースコードを元にして書かれています。
スキニングに必要なもの
CPU上でスキニング結果を取得できるComputeSkinnedPositions関数を見ればスキニングに必要なものは見えてきます。
void USkinnedMeshComponent::ComputeSkinnedPositions(
USkinnedMeshComponent* Component,
TArray<FVector> & OutPositions,
TArray<FMatrix>& CachedRefToLocals,
const FSkeletalMeshLODRenderData& LODData,
const FSkinWeightVertexBuffer& SkinWeightBuffer);
ComputeSkinnedPositions関数内部で呼ばれているGetTypedSkinnedVertexPosition関数で1頂点のスキニングができます。
template <bool bExtraBoneInfluencesT, bool bCachedMatrices>
FVector GetTypedSkinnedVertexPosition(
const USkinnedMeshComponent* SkinnedComp,
const FSkelMeshRenderSection& Section,
const FPositionVertexBuffer& PositionVertexBuffer,
const FSkinWeightVertexBuffer& SkinWeightVertexBuffer,
const int32 VertIndex,
const TArray<FMatrix> & RefToLocals);
templateでは追加ボーンの有無、つまり4ボーンスキニングか8ボーンスキニングかを静的に決定しています。これは頂点サイズを静的に決定するために使われます。またRefToLocalsという名前で表現されているボーン行列のキャッシュを利用するかどうかも静的に決定されます。単純化のためにRefToLocalsはキャッシュ済みとして話を進めます。
また、USkinnedMeshComponentの親子関係次第でMasterPoseComponentの影響を受けるのですが、こちらも単純化のため省略します。
本来なら頂点Indexに合わせてVertexを並べたバッファをGPUに送って計算するのですが、今回は省略しています。
FSkeletalMeshModel* Model = GetImportedModel();
for(const FSkeletalMeshLODModel& LodModel : Model->LODModels)
{
int32 AllSectionVertexIndex = 0;
for(const FSkelMeshSection& Section : LodModel.Sections)
{
// そのセクションでの開始頂点番号がBaseVertexIndex
check(Section.BaseVertexIndex == AllSectionVertexIndex);
// そのセクションでの頂点数がNumVertices
check(Section.NumVertices == Section.SoftVertices.Num());
for(const FSoftSkinVertex& SoftVertex : Section.SoftVertices)
{
FVector SkinnedPosition(0, 0, 0);
// ExtraBoneの定義に合わせてボーンインデックスが4か8存在する
const int32 MaxBoneInfluences = bExtraBoneInfluencesT ? MAX_TOTAL_INFLUENCES : MAX_INFLUENCES_PER_STREAM;
for(int32 InfluenceIndex = 0; InfluenceIndex < MaxBoneInfluences; InfluenceIndex++)
{
const uint8 BoneIndex = SoftVertex.InfluenceBones[InfluenceIndex];
const uint8 InfluenceWeight = SoftVertex.InfluenceWeights[InfluenceIndex];
// BoneIndexはそのまま使うのではなく、SectionのBoneMapを介して有用な値となる
const int32 MeshBoneIndex = Section.BoneMap[BoneIndex];
const FMatrix& RefToLocal = RefToLocals[MeshBoneIndex];
// uint8で格納されているので
const float Weight = StaticCast<float>(InfluenceWeight) / 255.0f;
SkinnedPosition += RefToLocal.TransformPosition(SoftVertex.Position) * Weight;
}
// 1頂点スキニング完了
AllSectionVertexIndex++;
}
}
}
GPUやらシェーダやらバッファの作成が絡んでくるとゲームスレッドやらレンダリングスレッドが出てきて複雑に感じますがスキニングだけに焦点を当ててみれば意外と簡単だったのではないでしょうか。
では良きUnreal C++を。