我是皮肤动画的新手,因此我花了一个月的时间来弄清楚为什么在对联合矩阵进行计算之后,动画当前无法输出正确的矩阵调色板。在GLTF 2.0皮肤教程中,它说明了如何计算用于蒙皮的关节矩阵:
jointMatrix(j) =
globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
globalTransformOfJointNode(j) *
inverseBindMatrixForJoint(j);
,所以我继续为我的动画引擎执行此操作:
void Animation::DoSampleJob(AnimJobSubmitInfo& job, r32 gt)
{
if (!job._output->_currState._bEnabled) { return; }
// This part is just calculating the local time progression, no issue here.
r32 tau = job._output->_currState._tau;
r32 rate = job._output->_currState._fPlaybackRate;
r32 lt = job._output->_currState._fCurrLocalTime + gt * rate;
if (lt > job._pBaseClip->_fDuration) {
lt -= job._pBaseClip->_fDuration;
job._output->_currState._tau = gt;
}
if (lt < 0.0f) {
lt = job._pBaseClip->_fDuration + lt;
if (lt < 0.0f) {
lt += job._pBaseClip->_fDuration;
job._output->_currState._tau = gt;
}
}
job._output->_currState._fCurrLocalTime = lt;
Skeleton* pSkeleton = Skeleton::GetSkeleton(job._pBaseClip->_skeletonId);
u32 currPoseIdx = 0;
u32 nextPoseIdx = 0;
GetCurrentAndNextPoseIdx(&currPoseIdx, &nextPoseIdx, job._pBaseClip, lt);
ApplyMorphTargets(job._output, job._pBaseClip, currPoseIdx, nextPoseIdx, lt);
if (EmptyPoseSamples(job._pBaseClip, currPoseIdx, nextPoseIdx)) { return; }
AnimPose* currAnimPose = &job._pBaseClip->_aAnimPoseSamples[currPoseIdx];
AnimPose* nextAnimPose = &job._pBaseClip->_aAnimPoseSamples[nextPoseIdx];
for (size_t i = 0; i < job._pBaseClip->_aAnimPoseSamples[currPoseIdx]._aLocalPoses.size(); ++i) {
JointPose* currJoint = &currAnimPose->_aLocalPoses[i];
JointPose* nextJoint = &nextAnimPose->_aLocalPoses[i];
Matrix4 localTransform = LinearInterpolate(currJoint, nextJoint, currAnimPose->_time, nextAnimPose->_time, lt);
job._output->_currentPoses[i] = localTransform;
}
ApplySkeletonPose(job._output->_finalPalette, job._output->_currentPoses, pSkeleton);
}
void Animation::ApplySkeletonPose(Matrix4* pOutput, Matrix4* pLocalPoses, Skeleton* pSkeleton)
{
if (!pSkeleton) return;
// This is where the issue is at, somewhere...
for (size_t i = 0; i < pSkeleton->_joints.size(); ++i) {
Matrix4 parentTransform;
Matrix4 currentPose;
u8 parentId = pSkeleton->_joints[i]._iParent;
if (parentId != Joint::kNoParentId) {
parentTransform = pLocalPoses[parentId];
}
// Now become world space joint matrices
currentPose = pLocalPoses[i] * parentTransform;
pLocalPoses[i] = currentPose;
}
for (size_t i = 0; i < pSkeleton->_joints.size(); ++i) {
pOutput[i] = pSkeleton->_joints[i]._InvBindPose * pLocalPoses[i] * pSkeleton->_joints[i]._invGlobalTransform;
}
}
进行投资,我继续删除了动画的计算,只是将关节矩阵计算为反向绑定姿势以及全局关节变换:
void Animation::ApplySkeletonPose(Matrix4* pOutput, Matrix4* pLocalPoses, Skeleton* pSkeleton)
{
if (!pSkeleton) return;
for (size_t i = 0; i < pSkeleton->_joints.size(); ++i) {
Matrix4 parentTransform;
Matrix4 currentPose;
u8 parentId = pSkeleton->_joints[i]._iParent;
if (parentId != Joint::kNoParentId) {
parentTransform = pLocalPoses[parentId];
}
// Now become work space joint matrices
currentPose = pLocalPoses[i] * parentTransform;
pLocalPoses[i] = currentPose;
}
for (size_t i = 0; i < pSkeleton->_joints.size(); ++i) {
// Just calculating only the inverse bind pose, and global joint transform, removing the current pose.
pOutput[i] = pSkeleton->_joints[i]._InvBindPose * pSkeleton->_joints[i]._invGlobalTransform.Inverse();
}
}
在不进行动画处理时,结果如预期的那样:
这就是我机智的地方。不确定为什么或如何通过计算关节来解决此问题。在应用逆绑定姿势和全局联合变换之前,在计算当前世界联合矩阵时,可能是我做不正确的事情吗?还是在那之前?我并不只专注于动画,而只专注于图形,但是从幕后了解所有工作原理是一件很整洁的事情:)。不幸的是,有很多方法可以进行动画蒙皮,因此我希望在此方面有所帮助,因为我在gltf中花了一个月的时间解决这个特定问题。非常感谢您的协助!
另外,您可能还需要看一下我如何解析皮肤,以及全局关节变形:
static skeleton_uuid_t LoadSkin(const tinygltf::Node& node, const tinygltf::Model& model, Model* engineModel, const Matrix4& parentMatrix)
{
if (node.skin == -1) return Skeleton::kNoSkeletonId;
Skeleton skeleton;
tinygltf::Skin skin = model.skins[node.skin];
b32 rootInJoints = false;
for (size_t i = 0; i < skin.joints.size(); ++i) {
if (skin.joints[i] == skin.skeleton) {
rootInJoints = true; break;
}
}
skeleton._joints.resize(skin.joints.size());
skeleton._name = skin.name;
skeleton._rootInJoints = rootInJoints;
const tinygltf::Accessor& accessor = model.accessors[skin.inverseBindMatrices];
const tinygltf::BufferView& bufView = model.bufferViews[accessor.bufferView];
const tinygltf::Buffer& buf = model.buffers[bufView.buffer];
struct NodeTag {
i32 _gltfParent;
u8 _parent;
Matrix4 _parentTransform;
};
std::map<i32, NodeTag> nodeMap;
for (size_t i = 0; i < skin.joints.size(); ++i) {
size_t idx = i;
Joint& joint = skeleton._joints[idx];
i32 skinJointIdx = skin.joints[i];
const tinygltf::Node& node = model.nodes[skinJointIdx];
NodeTransform localTransform;
auto it = nodeMap.find(skinJointIdx);
if (it != nodeMap.end()) {
NodeTag& tag = it->second;
localTransform = CalculateGlobalTransform(node, tag._parentTransform);
joint._iParent = tag._parent;
joint._invGlobalTransform = localTransform._globalMatrix.Inverse();
} else {
localTransform = CalculateGlobalTransform(node, Matrix4());
joint._iParent = 0xff;
joint._invGlobalTransform = localTransform._globalMatrix.Inverse();
}
DEBUG_OP(joint._id = static_cast<u8>(skinJointIdx));
for (size_t child = 0; child < node.children.size(); ++child) {
NodeTag tag = { static_cast<u8>(skinJointIdx), i, localTransform._globalMatrix };
nodeMap[node.children[child]] = tag;
}
}
const r32* bindMatrices = reinterpret_cast<const r32*>(&buf.data[bufView.byteOffset + accessor.byteOffset]);
for (size_t i = 0; i < accessor.count; ++i) {
Matrix4 invBindMat(&bindMatrices[i * 16]);
skeleton._joints[i]._InvBindPose = invBindMat;
}
Skeleton::PushSkeleton(skeleton);
engineModel->skeletons.push_back(Skeleton::GetSkeleton(skeleton._uuid));
return skeleton._uuid;
}
static void LoadNode(const tinygltf::Node& node, const tinygltf::Model& model, Model* engineModel, const Matrix4& parentMatrix, const r32 scale)
{
NodeTransform transform = CalculateGlobalTransform(node, parentMatrix);
if (!node.children.empty()) {
for (size_t i = 0; i < node.children.size(); ++i) {
LoadNode(model.nodes[node.children[i]], model, engineModel, transform._globalMatrix, scale);
}
}
if (node.skin != -1) {
skeleton_uuid_t skeleId = LoadSkin(node, model, engineModel, transform._globalMatrix);
Mesh* pMesh = LoadSkinnedMesh(node, model, engineModel, transform._globalMatrix);
pMesh->SetSkeletonReference(skeleId);
}
else {
LoadMesh(node, model, engineModel, transform._globalMatrix);
}
}
如果需要更多信息,请随时查看源代码 on my github.
非常感谢!