2012年9月16日 星期日

自訂骨架控制

UE的動畫樹(AnimTree)提供動畫混合、變形混合、骨架控制三種功能。它可以讓開發者自訂節點來擴充新的功能,在此篇簡單說明一下如何自訂骨架控制節點。

骨架控制節點的基礎類別叫做SkelControlBase,其他的骨架控制節點都會直接或間接繼承自此類別。它定義數個C++虛擬函式以供子類別擴充,以下列出重要的成員:
  • GetAffectedBones(BoneIndex, SkeletalMeshComponent, OutBoneIndices):可覆寫此函式傳回要修改的骨頭之索引值。
  • CalculateNewBoneTransforms(BoneIndex, SkeletalMeshComponent, OutBoneAtoms):可覆寫此函式傳回修改後的骨頭位置和方向。傳回的陣列必須和GetAffectedBones()相對應。
  • CalculateNewBoneScales(BoneIndex, SkeletalMeshComponent, OutBoneScales):可覆寫此函式傳回修改後的骨頭縮放值。傳回的陣列必須和GetAffectedBones()相對應。
  • GetBoneScale(BoneIndex, SkeletalMeshComponent):可覆寫此函式傳回此骨架控制的骨頭縮放值。
  • TickSkelControl(DeltaSeconds, SkeletalMeshComponent):可覆寫此函式每畫面更新骨架控制節點。
每個SkeletalMeshComponent會複製一份專用的動畫樹,所以即使許多模型使用同一個動畫樹範本,像是節點權重這種每個實例都要另記一份的資料,還是可以直接存放在骨架控制節點上。大部分骨架控制節點的功能是由C++實作,所以沒有原始碼授權的UDK使用者很難去擴充節點。

範例


原本SkelControlBase就有提供BoneScale欄位變更縮放值,不過子骨架也會跟著縮放,以下程式碼展示如何自訂一個可以不縮放子骨架的骨架控制節點:
class MySkelControl_Scale extends SkelControlBase
    native(Anim);
    
var() bool bScaleChildren;

cpptext
{
    // SkelControlBase interface
    virtual void GetAffectedBones(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<INT>& OutBoneIndices);
    virtual void CalculateNewBoneScales(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<FLOAT>& OutBoneScales);
}
GetAffectedBones()需要一併列出子骨架以便在CalculateNewBoneScales()修改:
void UMySkelControl_Scale::GetAffectedBones(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<INT>& OutBoneIndices)
{
    check( OutBoneIndices.Num() == 0 );
    check( SkelComp->SkeletalMesh );
    
    OutBoneIndices.AddItem(BoneIndex);
    
    if( ! bScaleChildren && BoneScale > KINDA_SMALL_NUMBER )
    {
        USkeletalMesh* SKelMesh = SkelComp->SkeletalMesh;
        INT BoneCount = SkelMesh->RefSkeleton.Num();
        
        for(INT i=BoneIndex+1; i<BoneCount; i++)
        {
            if( SkelMesh->RefSkeleton(i).ParentIndex = BoneIndex )
            {
                OutBoneIndices.AddItem(i);                
            }
        }
    }
}
將子骨架的縮放值設為倒數,抵消母骨頭的縮放效果:
void UMySkelControl_Scale::CalculateNewBoneScales(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<FLOAT>& OutBoneScales)
{
    check( OutBoneScales.Num() == 0 );
    check( SkelComp->SkeletalMesh );

    // The output array must correspond to the one output by GetAffectedBones().
    // The scale of this bone is applied in USkeletalMeshComponent::ApplyControllersForBoneIndex(), 
    // so do not scale it again.
    OutBoneScales.AddItem(1.f);
    
    if( ! bScaleChildren && BoneScale > KINDA_SMALL_NUMBER )
    {
        FLOAT InverseBoneScale = 1.f / BoneScale;
        
        USkeletalMesh* SKelMesh = SkelComp->SkeletalMesh;
        INT BoneCount = SkelMesh->RefSkeleton.Num();
        
        for(INT i=BoneIndex+1; i<BoneCount; i++)
        {
            if( SkelMesh->RefSkeleton(i).ParentIndex = BoneIndex )
            {
                OutBoneScales.AddItem(InverseBoneScale);                
            }
        }
    }
}

沒有留言:

張貼留言