Mesh is distorted(half-ish) after applying skinning

Started by
1 comment, last by dragon-kurve 5 years ago

I'm trying to implement skeletal animation using FBX SDK and OpenGL(with glm).

 

First of all, let me show results.

original.thumb.png.bc93a0e82b300f39546cb6ffd49da386.png

Above is original mesh, without animation.

skinned.thumb.png.6e9bb4587dc1933f81b0fcf7fb217e04.png

And this is the result.

 

My method to generate matrices is:


// joint is ordered by id, root id is 0
for (u8 i = 0; i < jointCount; ++i) {
	auto track = mCurrAnimation->track(i); // track is a list of key frames, which contains translation(vec3) and rotation(quat)

	auto& currKey = track->keys[mCurrKeyIndex]; // get current key frame
	auto& nextKey = track->keys[mNextKeyIndex]; // get next key frame
  
	auto& joint = mSkeleton->joints[i]; // get joint by id

	auto t = glm::mix(currKey.t, nextKey.t, alpha); // lerp translation
	auto q = glm::slerp(currKey.q, nextKey.q, alpha); // slerp rotation

	auto skinTransform = glm::translate(glm::mat4{1.0f}, t);
	skinTransform = skinTransform * glm::mat4_cast(q);
		
	glm::mat4 parentSkinTransform{1.0};
	if (joint.parentId > 0) { // if not root
		parentSkinTransform = mJointMatrices[joint.parentId];
	}

	mJointMatrices[i] = parentSkinTransform * skinTransform; // to parent space
	mSkinMatrices[i] = mJointMatrices[i] * joint.invBindPose; // inverse bind pose
}

I'm really struggling with this problem. As you can see, also left side of mesh is slightly distorted.

 

For more detail, these are how I got inverse bind pose and key frames from FBX.


// get joint inverse bind pose
if (const auto attrib = node->GetNodeAttribute();
	attrib) {
	const auto attribType = attrib->GetAttributeType();
	if (attribType == FbxNodeAttribute::eSkeleton) {
		CvtJoint cvtJoint{node->GetName(), id, parentId};
		cvtJoint.invBindPose = makeMat4(node->EvaluateGlobalTransform().Inverse());
		mCvtJointList.emplace_back(cvtJoint);
	}
}

// get key frames
for (auto frameIndex = startCount; frameIndex < stopCount; ++frameIndex) {
	FbxTime time{};
	time.SetFrame(frameIndex, timeMode);
	cvtAnimKey.time = static_cast<f32>(time.GetSecondDouble()); // get time of frame

	auto transform = node->EvaluateGlobalTransform(time); // get global transform at time
	if (const auto parent = node->GetParent(); // if node has parent
		parent) {
		auto parentTransform = parent->EvaluateGlobalTransform(time); // get parent global transform at time
		transform = parentTransform.Inverse() * transform; // to local space
	}
	cvtAnimKey.t = makeVec3(transform.GetT()); // FbxVector4 -> glm::vec3
	cvtAnimKey.q = makeQuat(transform.GetQ()); // FbxQuaternion -> glm::quat

	cvtAnimTrack.keys.emplace_back(cvtAnimKey); // add key to track
}

 

Any suggestion or advice is really appreciated...

Advertisement

UPDATE:

 

I found that my method to get vertex skin weight/joint index was completely wrong.


// joint index - vertex weight pair
// Which is compelety wrong.
std::vector<std::vector<std::pair<glm::u8, glm::f32>>> skinData{};
skinData.resize(mesh->GetControlPointsCount());

const auto deformerCount = mesh->GetDeformerCount(FbxDeformer::eSkin);
for (int deformerIndex = 0; deformerIndex < deformerCount; ++deformerIndex) {
	auto skin = FbxCast<FbxSkin>(mesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));

	const auto clusterCount = skin->GetClusterCount();
	for (int clusterIndex = 0; clusterIndex < clusterCount; ++clusterIndex) {
		const auto cluster = skin->GetCluster(clusterIndex);
		const auto link = cluster->GetLink();
		const auto linkName = link->GetName();

		const auto it = std::find_if(mCvtSkeleton.mJointList.begin(), mCvtSkeleton.mJointList.end(), [linkName](const auto& cvtJoint) {
			return cvtJoint.name == linkName;
		});
		if (it == mCvtJointList.end()) {
			continue;
		
		// transformation of joint
		FbxAMatrix transformLinkMatrix{};
		cluster->GetTransformLinkMatrix(transformLinkMatrix);
		// transformation of mesh
		FbxAMatrix transformMatrix{};
		cluster->GetTransformMatrix(transformMatrix)
		it->invBindPose = makeMat4(transformLinkMatrix.Inverse() * transformMatrix);

		const auto skinIndicesCount = cluster->GetControlPointIndicesCount();
		const auto skinIndices = cluster->GetControlPointIndices();
		const auto skinWeights = cluster->GetControlPointWeights();
		for (int skinIndex = 0; skinIndex < skinIndicesCount; ++skinIndex) {
            	// This is the wrong part. I should use control point index instead of joint id.
			skinData[skinIndices[skinIndex]].emplace_back(it->id, makeF32(skinWeights[skinIndex]));
		}
	}
}

Then later, when I call

FbxMesh::GetPolygonVertex(int pPolygonIndex, int pPositionInPolygon ) const -> int

that returns control point index.

 

That means I need someting like this


struct CvtSkinInfo
{
	glm::u8 mJointId;
	glm::u16 mCpIndex;
	glm::f32 mCpWeight;
};

to correctly get joint id and vertex weight of a control point.

 

I'll fix this and share again.

This topic is closed to new replies.

Advertisement