Direct X 12 – Character Animation 角色动画

23.5 Character Animation Demo

正如我们之前看到的蒙皮网格模型的着色器代码,每根骨头的final转换矩阵存储在constant buffer中,而我们可以在顶点着色器中读取这些数据,用作动画的转换。

// HLSL
cbuffer cbSkinned : register(b1)
{
    // 每个角色最多有96根骨头
    float4x4 gBoneTransforms[96];
};

因此我们需要将这些新增的constant buffer添加到每一个蒙皮网格模型对象中,也就是添加到frame resource中:

struct SkinnedConstants
{
    DirectX::XMFLOAT4X4 BoneTransforms[96];
};

std::unique_ptr<UploadBuffer<SkinnedConstants>> SkinnedCB = nullptr;
SkinnedCB = std::make_unique<UploadBuffer<SkinnedConstants>>(
    device, skinnedObjectCount, true);

每一个动画角色都需要一个SkinnedConstants。一般来说,一个动画角色会有多个render-item组成(每一个render-item对应一种材质),但是同一个动画角色的所有render-item可以分享同一个SkinnedConstants(因为它们使用同一个动画骨骼)。

为了表示动画角色的实例,我们定义了以下结构体:

struct SkinnedModelInstance
{
    SkinnedData* SkinnedInfo = nullptr;

    // 基于时间点存储了final转换矩阵
    std::vector<DirectX::XMFLOAT4X4> FinalTransform;

    // 当前的animation clip
    std::string ClipName;

    // 动画的时间点
    float TimePos = 0.f;

    // 每一帧被调用以播放动画
    void UpdateSkinnedAnimation(float dt)
    {
        TimePos += dt;

        // 循环动画
        if(TimePos > SkinnedInfo->GetClipEndTime(ClipName))
            TimePos = 0.f;
        
        // 每一帧都被调用并且对时间点进行累加,
        // 基于当前animation clip,对每根骨头的动画进行插值,
        // 并且生成final转换矩阵,顶点着色器将使用该矩阵
        SkinnedInfo->GetFinalTransform(ClipName, TimePos, FinalTransforms);
    }
};

之后,我们将以下成员变量加入结构体RenderItem:

struct RenderItem
{
    ...

    // 骨头转换矩阵的constant buffer的索引
    // 只运用于蒙皮render item
    UINT SkinnedCBIndex = -1;

    // 指向该render item所使用的动画实例的指针,
    // 如果render item不是由蒙皮网格模型驱动,那么其是一个空指针
    SkinnedModelInstance* SkinnedModelInst = nullptr;

    ...
};

每一帧我们都会更新动画角色的实例(在demo中,我们只有一个动画角色):

void SkinnedMeshApp::UpdateSkinnedCBs(const GameTimer& gt)
{
    auto currSkinnedCB = mCurrFrameResource->SkinnedCB.get();

    // 在demo中我们只有一个蒙皮模型会被动画驱动
    mSkinnedModelInst->UpdateSkinnedAnimation(gt.DeltaTime());

    SkinnedConstants skinnedCOnstants;
    std::copy(
        std::begin(mSkinnedModelInst->FinalTransforms),
        std::end(mSkinnedModelInst->FinalTransforms),
        &skinnedConstants.BoneTransforms[0]);
    currSkinnedCB->CopyData(0, skinnedConstants);
}

当我们绘制render-item时,如果该render item有蒙皮网格模型,那么我们将其对应的骨骼的final转换矩阵进行绑定:

if(ri->SkinnedModelInst != nullptr)
{
    D3D12_GPU_VIRTUAL_ADDRESS skinnedCBAddress = 
        skinnedCB->GetGPUVirtualAddress() +
        ri->SkinnedCBIndex * skinnedCBByteSize;
    cmdList->SetGraphicsRootConstantBufferView(1, skinnedCBAddress);
}
else
{
    cmdList->SetGraphicsRootConstantBufferView(1, 0);
}

以下为本章节demo的截图:

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据