FBX SDK 骨骼动画

4

我已成功使用fbx sdk和directx加载和渲染了一个fbx模型。

现在我正在尝试实现骨骼动画。首先,我正在尝试通过修改骨骼矩阵来使我的角色处于不同的姿势。(这样我就可以通过在不同的时间设置不同的姿势来实现动画)

以下是我加载fbx和皮肤信息的代码:

FBXLoader.h

#include <fbxsdk.h>
#include <vector>
#include <string>
#include <map>
#include <d3d11_1.h>
#include <DirectXMath.h>
#include "TextureLoader.h"

using namespace DirectX;

struct VERTEX
{
    XMFLOAT3 pos;
    XMFLOAT2 tex;
    XMFLOAT4 boneids;
    XMFLOAT4 weights;

    VERTEX()
    {
        boneids = { 0, 0, 0, 0 };
        weights = { 0, 0, 0, 0 };
    }
};

struct Keyframe {
    FbxLongLong mFrameNum;
    FbxAMatrix mGlobalTransform;
    Keyframe* mNext;

    Keyframe() : mNext(nullptr)
    {}
};

struct Joint {
int mParentIndex;
const char* mName;
FbxAMatrix mGlobalBindposeInverse;
Keyframe* mAnimation;
FbxNode *mNode;

Joint() :
    mNode(nullptr),
    mAnimation(nullptr)
{
    mGlobalBindposeInverse.SetIdentity();
    mParentIndex = -1;
}

~Joint()
{
    while (mAnimation)
    {
        Keyframe* temp = mAnimation->mNext;
        delete mAnimation;
        mAnimation = temp;
    }
}
};

struct Skeleton {
std::vector<Joint> mJoints;
};

class Mesh
{
public:
Mesh(ID3D11Device *dev, std::vector<VERTEX> vertices, ID3D11ShaderResourceView *texture)
{
    this->vertices = vertices;
    this->texture = texture;

    this->SetupMesh(dev);
}

void Draw(ID3D11DeviceContext *devcon)
{
    UINT stride = sizeof(VERTEX);
    UINT offset = 0;

    devcon->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

    if (this->texture != nullptr)
        devcon->PSSetShaderResources(0, 1, &texture);

    devcon->Draw(vertices.size(), 0);
}
private:
std::vector<VERTEX> vertices;
ID3D11ShaderResourceView *texture = nullptr;
ID3D11Buffer* vertexBuffer;

bool SetupMesh(ID3D11Device *dev)
{
    HRESULT hr;

    D3D11_BUFFER_DESC vbd;
    vbd.Usage = D3D11_USAGE_IMMUTABLE;
    vbd.ByteWidth = sizeof(VERTEX) * vertices.size();
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    vbd.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA initData;
    initData.pSysMem = &vertices[0];

    hr = dev->CreateBuffer(&vbd, &initData, &vertexBuffer);
    if (FAILED(hr))
        return false;
}
};

class FBXLoader
{
public:
FBXLoader();
~FBXLoader();

void LoadFBX(HWND hwnd, ID3D11Device *dev, ID3D11DeviceContext *devcon, const char* filename);

void Draw(ID3D11DeviceContext *devcon);

XMMATRIX GetAnimatedMatrix(int index);

Skeleton skeleton;

private:
FbxManager *fbxsdkManager = nullptr;
FbxScene *fbxScene;
std::map<int, int> controlpoints;
std::vector<Mesh> meshes;
HWND hwnd;

void ProcessNode(ID3D11Device *dev, ID3D11DeviceContext *devcon, FbxNode *node, FbxGeometryConverter *gConverter);

Mesh ProcessMesh(ID3D11Device* dev, ID3D11DeviceContext *devcon, FbxMesh *mesh);

void ProcessSkeletonHeirarchy(FbxNode* rootnode);

void ProcessSkeletonHeirarchyre(FbxNode* node, int depth, int index, int parentindex);

unsigned int FindJointIndex(const std::string& jointname);

ID3D11ShaderResourceView *LoadTexture(ID3D11Device *dev, ID3D11DeviceContext *devcon, const char* texturefilename);
};

FBXLoader.cpp

#include "FBXLoader.h"



FBXLoader::FBXLoader()
{
}


FBXLoader::~FBXLoader()
{
}

void FBXLoader::LoadFBX(HWND hwnd, ID3D11Device * dev, ID3D11DeviceContext *devcon, const char * filename)
{
if (fbxsdkManager == nullptr)
{
    fbxsdkManager = FbxManager::Create();

    FbxIOSettings* ioSettings = FbxIOSettings::Create(fbxsdkManager, IOSROOT);
    fbxsdkManager->SetIOSettings(ioSettings);
}

FbxImporter *importer = FbxImporter::Create(fbxsdkManager, "");
fbxScene = FbxScene::Create(fbxsdkManager, "");

FbxGeometryConverter gConverter(fbxsdkManager);

bool bSuccess = importer->Initialize(filename, -1, fbxsdkManager->GetIOSettings());

bSuccess = importer->Import(fbxScene);

importer->Destroy();

FbxNode *fbxRootNode = fbxScene->GetRootNode();

ProcessSkeletonHeirarchy(fbxRootNode);

this->hwnd = hwnd;

ProcessNode(dev, devcon, fbxRootNode, &gConverter);
}

void FBXLoader::Draw(ID3D11DeviceContext * devcon)
{
for (int i = 0; i < meshes.size(); i++)
{
    meshes[i].Draw(devcon);
}
}

XMMATRIX FBXLoader::GetAnimatedMatrix(int index)
{
XMMATRIX bonematxm;
FbxAMatrix bonemat = skeleton.mJoints[index].mGlobalBindposeInverse; //* skeleton.mJoints[0].mAnimation->mGlobalTransform;

bonematxm = XMMatrixTranslation(bonemat.GetT().mData[0], bonemat.GetT().mData[1], bonemat.GetT().mData[2]);
bonematxm *= XMMatrixRotationX(bonemat.GetR().mData[0]);
bonematxm *= XMMatrixRotationY(bonemat.GetR().mData[1]);
bonematxm *= XMMatrixRotationZ(bonemat.GetR().mData[2]);

return bonematxm;
}

void FBXLoader::ProcessNode(ID3D11Device * dev, ID3D11DeviceContext *devcon, FbxNode * node, FbxGeometryConverter * gConverter)
{
if (node)
{
    if (node->GetNodeAttribute() != nullptr)
    {
        FbxNodeAttribute::EType AttributeType = node->GetNodeAttribute()->GetAttributeType();

        if (AttributeType == FbxNodeAttribute::eMesh)
        {
            FbxMesh *mesh;

            mesh = (FbxMesh*)gConverter->Triangulate(node->GetNodeAttribute(), true);

            meshes.push_back(ProcessMesh(dev, devcon, mesh));
        }
    }

    for (int i = 0; i < node->GetChildCount(); i++)
    {
        ProcessNode(dev, devcon, node->GetChild(i), gConverter);
    }
}
}

Mesh FBXLoader::ProcessMesh(ID3D11Device * dev, ID3D11DeviceContext *devcon, FbxMesh * mesh)
{
std::vector<VERTEX> meshvertices;
ID3D11ShaderResourceView *meshtexture = nullptr;

FbxVector4 *vertices = mesh->GetControlPoints();

for (int j = 0; j < mesh->GetPolygonCount(); j++)
{
    int numVertices = mesh->GetPolygonSize(j);

    FbxLayerElementArrayTemplate<FbxVector2> *uvVertices = 0;
    mesh->GetTextureUV(&uvVertices, FbxLayerElement::eTextureDiffuse);

    for (int k = 0; k < numVertices; k++)
    {
        int controlPointIndex = mesh->GetPolygonVertex(j, k);

        VERTEX vertex;

        vertex.pos.x = (float)vertices[controlPointIndex].mData[0];
        vertex.pos.y = (float)vertices[controlPointIndex].mData[1];
        vertex.pos.z = (float)vertices[controlPointIndex].mData[2];

        vertex.tex.x = (float)uvVertices->GetAt(mesh->GetTextureUVIndex(j, k)).mData[0];
        vertex.tex.y = 1.0f - (float)uvVertices->GetAt(mesh->GetTextureUVIndex(j, k)).mData[1];

        controlpoints[controlPointIndex] = meshvertices.size();

        meshvertices.push_back(vertex);
    }
}

int materialcount = mesh->GetNode()->GetSrcObjectCount<FbxSurfaceMaterial>();

for (int i = 0; i < materialcount; i++)
{
    FbxSurfaceMaterial *material = (FbxSurfaceMaterial*)mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(i);

    if (material)
    {
        FbxProperty prop = material->FindProperty(FbxSurfaceMaterial::sDiffuse);

        const FbxTexture* texture = FbxCast<FbxTexture>(prop.GetSrcObject<FbxTexture>(0));
        const FbxFileTexture* filetexture = FbxCast<FbxFileTexture>(texture);

        ID3D11ShaderResourceView *meshctexture = LoadTexture(dev, devcon, filetexture->GetFileName());

        meshtexture = meshctexture;
    }
}

const FbxVector4 lT = mesh->GetNode()->GetGeometricTranslation(FbxNode::eSourcePivot);
const FbxVector4 lR = mesh->GetNode()->GetGeometricRotation(FbxNode::eSourcePivot);
const FbxVector4 lS = mesh->GetNode()->GetGeometricScaling(FbxNode::eSourcePivot);

FbxAMatrix geometryTransform = FbxAMatrix(lT, lR, lS);

for (unsigned int deformerIndex = 0; deformerIndex < mesh->GetDeformerCount(); ++deformerIndex)
{
    FbxSkin* skin = reinterpret_cast<FbxSkin*>(mesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));
    if (!skin)
        continue;

    for (unsigned int clusterIndex = 0; clusterIndex < skin->GetClusterCount(); ++clusterIndex)
    {
        FbxCluster* cluster = skin->GetCluster(clusterIndex);
        std::string jointname = cluster->GetLink()->GetName();
        unsigned int jointIndex = FindJointIndex(jointname);
        FbxAMatrix transformMatrix;
        FbxAMatrix transformLinkMatrix;
        FbxAMatrix globalBindposeInverseMatrix;

        cluster->GetTransformMatrix(transformMatrix);
        cluster->GetTransformLinkMatrix(transformLinkMatrix);
        globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix * geometryTransform;

        skeleton.mJoints[jointIndex].mGlobalBindposeInverse = globalBindposeInverseMatrix;
        skeleton.mJoints[jointIndex].mNode = cluster->GetLink();

        for (unsigned int i = 0; i < cluster->GetControlPointIndicesCount(); ++i)
        {
            int vertexid = controlpoints[cluster->GetControlPointIndices()[i]];

            if (meshvertices[vertexid].boneids.x == 0) meshvertices[vertexid].boneids.x = jointIndex;
            if (meshvertices[vertexid].boneids.y == 0) meshvertices[vertexid].boneids.y = jointIndex;
            if (meshvertices[vertexid].boneids.z == 0) meshvertices[vertexid].boneids.z = jointIndex;
            if (meshvertices[vertexid].boneids.w == 0) meshvertices[vertexid].boneids.w = jointIndex;
            if (meshvertices[vertexid].weights.x == 0) meshvertices[vertexid].weights.x = cluster->GetControlPointWeights()[i];
            if (meshvertices[vertexid].weights.y == 0) meshvertices[vertexid].weights.y = cluster->GetControlPointWeights()[i];
            if (meshvertices[vertexid].weights.z == 0) meshvertices[vertexid].weights.z = cluster->GetControlPointWeights()[i];
            if (meshvertices[vertexid].weights.w == 0) meshvertices[vertexid].weights.w = cluster->GetControlPointWeights()[i];
        }

        FbxAnimStack* animstack = fbxScene->GetSrcObject<FbxAnimStack>(0);
        FbxString animstackname = animstack->GetName();
        FbxTakeInfo* takeinfo = fbxScene->GetTakeInfo(animstackname);
        FbxTime start = takeinfo->mLocalTimeSpan.GetStart();
        FbxTime end = takeinfo->mLocalTimeSpan.GetStop();
        FbxLongLong animationlength = end.GetFrameCount(FbxTime::eFrames30) - start.GetFrameCount(FbxTime::eFrames30) + 1;
        Keyframe** anim = &skeleton.mJoints[jointIndex].mAnimation;

        for (FbxLongLong i = start.GetFrameCount(FbxTime::eFrames30); i <= end.GetFrameCount(FbxTime::eFrames30); ++i)
        {
            FbxTime time;
            time.SetFrame(i, FbxTime::eFrames30);
            *anim = new Keyframe();
            (*anim)->mFrameNum = i;
            FbxAMatrix transformoffset = mesh->GetNode()->EvaluateGlobalTransform(1.0f) * geometryTransform;
            (*anim)->mGlobalTransform = transformoffset.Inverse() * cluster->GetLink()->EvaluateGlobalTransform(time);
            anim = &((*anim)->mNext);
        }
    }
}

return Mesh(dev, meshvertices, meshtexture);
}

void FBXLoader::ProcessSkeletonHeirarchy(FbxNode * rootnode)
{
for (int childindex = 0; childindex < rootnode->GetChildCount(); ++childindex)
{
    FbxNode *node = rootnode->GetChild(childindex);
    ProcessSkeletonHeirarchyre(node, 0, 0, -1);
}
}

void FBXLoader::ProcessSkeletonHeirarchyre(FbxNode * node, int depth, int index, int parentindex)
{
if (node->GetNodeAttribute() && node->GetNodeAttribute()->GetAttributeType() && node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eSkeleton)
{
    Joint joint;
    joint.mParentIndex = parentindex;
    joint.mName = node->GetName();
    skeleton.mJoints.push_back(joint);
}
for (int i = 0; i < node->GetChildCount(); i++)
{
    ProcessSkeletonHeirarchyre(node->GetChild(i), depth + 1, skeleton.mJoints.size(), index);
}
}

unsigned int FBXLoader::FindJointIndex(const std::string & jointname)
{
for (unsigned int i = 0; i < skeleton.mJoints.size(); ++i)
{
    if (skeleton.mJoints[i].mName == jointname)
    {
        return i;
    }
}
}

ID3D11ShaderResourceView * FBXLoader::LoadTexture(ID3D11Device * dev, ID3D11DeviceContext * devcon, const char * texturefilename)
{
HRESULT hr;

ID3D11ShaderResourceView *texture;

std::string filenamestr(texturefilename);
std::string sl = "/";
size_t start_pos = filenamestr.find(sl);
filenamestr.replace(start_pos, sl.length(), "\\");
std::wstring filename = std::wstring(filenamestr.begin(), filenamestr.end());

hr = CreateWICTextureFromFile(dev, devcon, filename.c_str(), nullptr, &texture);
if (FAILED(hr))
    return nullptr;

return texture;
}

这是我的顶点着色器。
matrix bonetransform = mul(bones[boneids[0]], weights[0]);
bonetransform += mul(bones[boneids[1]], weights[1]);
bonetransform += mul(bones[boneids[2]], weights[2]);
bonetransform += mul(bones[boneids[3]], weights[3]);

float4 posl = mul(bonetransform, pos);

output.pos = mul(posl, World);

这是我的绘图代码。
cb.mWorld = XMMatrixTranspose(m_World);
cb.mView = XMMatrixTranspose(m_View);
cb.mProjection = XMMatrixTranspose(m_Projection);
for (int i = 0; i < jasper->skeleton.mJoints.size(); i++)
    cb.bones[i] = XMMatrixTranspose(XMMatrixIdentity());
devcon->UpdateSubresource(pConstantBuffer, 0, nullptr, &cb, 0, 0);

devcon->VSSetShader(pVS, 0, 0);
devcon->VSSetConstantBuffers(0, 1, &pConstantBuffer);
devcon->PSSetShader(pPS, 0, 0);
devcon->PSSetSamplers(0, 1, &TexSamplerState);
model->Draw(devcon);

当我在顶点着色器中将所有骨骼矩阵设置为单位矩阵时,我得到了这个结果。应该在使用单位矩阵时得到这个结果吗?请参考以下图片。
3个回答

2
我把这个问题顶上来,因为我有同样的问题。以下是类似任务的实验文档,希望能帮到你:
从FBX获取关节动画数据 这个过程将允许我们从FBX关节层次结构中获取动画数据。你必须先完成这个过程的前一步(“在FBX中获取BindPose的关节变换”)。
  1. 从FbxScene实例中获取第一个FbxAnimStack a. FbxScene可能有多层动画,但我们只关注其中一层 b. 有用的函数 i. FbxScene::GetCurrentAnimationStack()
  2. 获取动画的持续时间 a. 有用的函数 i. FbxAnimStack::GetLocalTimeSpan() ii. FbxTime::GetDuration()
  3. 获取所需时间模式下动画的帧数 a. 有用的函数 i. FbxTime::GetFrameCount(FbxTime::EMode) FbxTime::EMode::eFrames24表示每秒24帧,并建议使用
  4. 剩余步骤将引用您的FbxNode*/父索引对的动态数组 a. 示例:struct my_fbx_joint { FbxNode* node; int parent_index;}; b. 示例:std::vector< my_fbx_joint >;
  5. 您需要一个关键帧类型 a. 示例:struct keyframe { double time; std::vector joints; };
  6. 您需要一个动画剪辑类型 a. 示例:struct anim_clip { double duration; std::vector frames; };
  7. 创建一个动画剪辑以填充
  8. 对于帧数(跳过帧号0的BindPose) a. 创建一个关键帧对象 b. 确定此帧的FbxTime i. FbxTime::SetFrame(FbxLongLong, FbxMode::EMode) c. 将关键时间存储在当前关键帧中 d. 对于第I部分中关节数组中的每个FbxNode* i. 获取该节点在此帧上的全局变换
  9. 有用的函数 a. FbxNode::EvaluateGlobalTransform(FbxTime) ii. 将此变换添加到关键帧的关节中
  10. 保持索引位置一致。关节在关键帧中应与BindPose中的索引相同 e. 将关键帧添加到动画剪辑中
编辑:我还要补充一点(取决于你使用的拓扑结构),检查你使用的网格是否三角化,因为这在过去解决了我的问题。

0

先尝试用线条只画出你的骨架......这可能有所帮助,在画出骨架和角色之后!


0
不正确的呈现原因是控制点在顶点之间共享,但在上述代码中每个控制点只分配了一个顶点。同一控制点最多可以连接8个顶点。 一种解决方案是将controlpoints更改为std::map<int,std::vector<int>>,然后在processMesh方法中使用该数据结构。
controlpoints[controlPointIndex] = meshvertices.size();

你可以这样做

controlpoints[controlPointIndex].push_back(meshvertices.size())

当你在循环遍历皮肤时,你可以像这样做:
auto vertexids = controlpoints[cluster->GetControlPointIndices()[i]];

for (auto& vertexid : vertexids) {
...
}

我知道这个问题已经超过5年了,但我仍然认为我的答案对其他人可能有用,就像对我一样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接