wub*_*ubw 6 c++ animation gltf
我是皮肤动画的新手,所以在对联合矩阵执行计算后,我花了一个月的时间试图找出为什么我的动画当前没有输出正确的矩阵调色板。在GLTF 2.0蒙皮教程中,它说明了如何计算蒙皮的关节矩阵:
jointMatrix(j) =
globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
globalTransformOfJointNode(j) *
inverseBindMatrixForJoint(j);
Run Code Online (Sandbox Code Playgroud)
所以我继续为我的动画引擎执行此操作:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
经过研究,我继续删除动画的计算,只将关节矩阵计算为逆绑定姿势以及全局关节变换:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
当不设置动画时,结果正如预期的那样:
这就是我束手无策的地方。不确定为什么或如何通过计算我的关节来解决这个问题。在应用逆绑定姿势和全局关节变换之前,我在计算当前世界关节矩阵时可能没有正确执行某些操作吗?或者甚至在此之前的事情?我不专注于动画,只专注于图形,但了解它在幕后是如何工作的是一件很有趣的事情:)。不幸的是,有很多方法可以实现动画蒙皮,所以我希望能在这方面找到一些帮助,因为我在 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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果需要更多信息,请随时查看 我的 github 上的源代码。
非常感谢!