IAS0601
IAS0601

Reputation: 300

FBX Sdk Skeletal Animations

I have successfully loaded and rendered a fbx model using fbx sdk and directx. I'm now trying to implement skeletal animations. First I'm trying to put my character on different poses by modifying bones matrices. (So I can achieve animations by setting different poses at times)

Here's my code of loading fbx and skinning information.

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;
}

Here's my vertex shader

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);

Here's my drawing code

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);

And when I set all bones matrices in vertex shader to identity matrix this is what I get.

enter image description here

Shouldn't it be this when I use identity matrix?

enter image description here

Upvotes: 4

Views: 7146

Answers (3)

Csaba Varga
Csaba Varga

Reputation: 1

The reason of the incorrect render is that control points are shared between vertices, but in the above code only one vertex is assigned per controlpoint. The same control point can connect up to 8 vertices. One solution would be to change controlpoints to std::map<int, std::vector<int>> and then in the processMesh method instead of

controlpoints[controlPointIndex] = meshvertices.size();

you could do

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

and when you are looping through the skins you can do something like this:

auto vertexids = controlpoints[cluster->GetControlPointIndices()[i]];

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

I know this question is more than 5 years old, but I still think my answer could be useful for someone else, as it would have been for me.

Upvotes: 0

Romerik Rousseau
Romerik Rousseau

Reputation: 97

try drawing only your skeleton with lines first... that might help after draw both, the skeleton & the character!

Upvotes: 0

user2073308
user2073308

Reputation: 57

Bumping this as I have the same question. Here is the lab document for an assignment similar. Hopefully this helps:

Getting Joint Animation Data From FBX This process will allow us to get animation data from an FBX joint hierarchy. You must have the previous step of this process completed first ("Getting the Joint Transforms for a BindPose in FBX").

  1. From your FbxScene instance, get the first FbxAnimStack a. An FbxScene may have layers of animation, but we are focusing on just one b. Helpful functions i. FbxScene::GetCurrentAnimationStack()
  2. Get the duration of the animation a. Helpful functions i. FbxAnimStack::GetLocalTimeSpan() ii. FbxTime::GetDuration()
  3. Get the number of frames in the animation for the desired time mode a. Helpful functions i. FbxTime::GetFrameCount(FbxTime::EMode) FbxTime::EMode::eFrames24 gives 24 frames per second, and is recommended
  4. The remaining steps will reference your dynamic array of FbxNode*/parent index pairs a. Ex: struct my_fbx_joint { FbxNode* node; int parent_index;}; b. Ex: std::vector< my_fbx_joint >;
  5. You will need a keyframe type a. Ex: struct keyframe { double time; std::vector joints; };
  6. You will need an animation clip type a. Ex: struct anim_clip { double duration; std::vector frames; };
  7. Create an anim_clip to fill
  8. For the number of frames (skipping the bind pose at frame number 0) a. Create a keyframe object b. Determine the FbxTime for this frame i. FbxTime::SetFrame(FbxLongLong, FbxMode::EMode) c. Store the key time in the current keyframe d. For each FbxNode* in your joint array from part I i. Get the node's evaluated global transform for this frame
  9. Helpful function a. FbxNode::EvaluateGlobalTransform(FbxTime) ii. Add this transform to the keyframe's joints
  10. Keep index locations consistent. Joints should have the same index in a keyframe as they do in a bind pose e. Add the keyframe to the anim_clip

Edit:: I would also add (depending on what topology you are using) to check if the mesh you are using is triangulated as that has fixed issues like this in the past for me.

Upvotes: 2

Related Questions