Я новичок в анимации скинов, поэтому я провел хороший месяц, пытаясь выяснить, почему моя анимация в настоящее время не выводит правильную матричную палитру, после выполнения расчетов на моих совместных матрицах. В руководствах по скину 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);
}
}
Если требуется дополнительная информация, пожалуйста, не стесняйтесь увидеть исходный код
на моем github.
Большое спасибо!
Задача ещё не решена.
Других решений пока нет …