Reputation: 300
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.
Shouldn't it be this when I use identity matrix?
Upvotes: 4
Views: 7146
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
Reputation: 97
try drawing only your skeleton with lines first... that might help after draw both, the skeleton & the character!
Upvotes: 0
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").
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