Я нахожусь в процессе написания системы анимации со своим собственным парсером Collada и столкнулся с проблемой, которую не могу понять.
Я собрал информацию о своей сетке / скине (вершины, нормали, JointIds, веса, и т. д. c) , информация о моем скелете (суставы, их локальные преобразования, обратная позиция привязки, структура иерархии) и моя анимация ( позиция преобразования ключевого кадра для каждого сустава, временная метка) .
Моя проблема в том, что когда все вычисляется и затем реализуется в шейдере (сумма весов, умноженная на совместное преобразование и положение вершины), я получаю следующее:
When I remove the weight multiplication, the mesh remains fully intact - however the skin doesn't actually follow the animation. I am at a lost as I feel as though the math is correct, but very obviously I am going wrong somewhere. Would someone be able to shine light on the aspect I have misinterpreted?
Here is my current understanding and implementation:
- After collecting all of the joint's localTransforms and hierarchy, I calculate their inverse bind transfromation matrix. To do this I multiple each joints localTransform with their parentLocalTransform to get a bindTransform. Inverting that bindTransform results in their inverseBindTransform. Below is my code for that:
// Recursively collect each Joints InverseBindTransform -
// root joint's local position is an identity matrix.
// Function is only called once after data collection.
void Joint::CalcInverseBindTransform(glm::mat4 parentLocalPosition)
{
glm::mat4 bindTransform = parentLocalPosition * m_LocalTransform;
m_InverseBindPoseMatrix = glm::inverse(bindTransform);
for (Joint child : Children) {
child.CalcInverseBindTransform(bindTransform);
}
}
- В моем аниматоре во время анимации для каждого сустава я беру два JointTransforms для двух кадров, между которыми находится мой currentTime, и я вычисляю интерполированный JointTransform. (JointTransform просто имеет vec3 для позиции и кватернион для вращения). Я делаю это для каждого соединения, а затем применяю эти интерполированные значения к каждому соединению, снова рекурсивно умножая новый frameLocalTransform на их parentLocalTransform. Я беру этот bindTransform и умножаю его на invBindTransform, а затем транспонирую матрицу. Ниже приведен код для этого:
std::unordered_map<int, glm::mat4> Animator::InterpolatePoses(float time) {
std::unordered_map<int, glm::mat4> poses;
if (IsPlaying()) {
for (std::pair<int, JointTransform> keyframe : m_PreviousFrame.GetJointKeyFrames()) {
JointTransform previousFrame = m_PreviousFrame.GetJointKeyFrames()[keyframe.first];
JointTransform nextFrame = m_NextFrame.GetJointKeyFrames()[keyframe.first];
JointTransform interpolated = JointTransform::Interpolate(previousFrame, nextFrame, time);
poses[keyframe.first] = interpolated.getLocalTransform();
}
}
return poses;
}
void Animator::ApplyPosesToJoints(std::unordered_map<int, glm::mat4> newPose, Joint* j, glm::mat4 parentTransform)
{
if (IsPlaying()) {
glm::mat4 currentPose = newPose[j->GetJointId()];
glm::mat4 modelSpaceJoint = parentTransform * currentPose;
for (Joint child : j->GetChildren()) {
ApplyPosesToJoints(newPose, &child, modelSpaceJoint);
}
modelSpaceJoint = glm::transpose(j->GetInvBindPosition() * modelSpaceJoint);
j->SetAnimationTransform(modelSpaceJoint);
}
}
Затем я собираю все новые AnimatedTransforms для каждого сустава и отправляю их шейдеру:
void AnimationModel::Render(bool& pass)
{
[...]
std::vector<glm::mat4> transforms = GetJointTransforms();
for (int i = 0; i < transforms.size(); ++i) {
m_Shader->SetMat4f(transforms[i], ("JointTransforms[" + std::to_string(i) + "]").c_str());
}
[...]
}
void AnimationModel::AddJointsToArray(Joint current, std::vector<glm::mat4>& matrix)
{
glm::mat4 finalMatrix = current.GetAnimatedTransform();
matrix.push_back(finalMatrix);
for (Joint child : current.GetChildren()) {
AddJointsToArray(child, matrix);
}
}
В шейдере я просто следую формуле суммирования, которую можно найти в Интернете при исследовании этого топа c:
for (int i = 0; i < total_weight_amnt; ++i) {
mat4 jointTransform = JointTransforms[jointIds[i]];
vec4 newVertexPos = jointTransform * vec4(pos, 1.0);
total_pos += newVertexPos * weights[i];
[...]
--------- - Ответ на нормализацию весов ------------
Было несколько весов, суммирующих больше 1, но после устранения ошибки в моем коде модель выглядела так:
введите описание изображения здесь
Для расчета весов - I l oop через все предварительно добавленные веса в векторе, и если я найду вес, который меньше веса, который я хочу добавить, я заменяю этот вес в этом положении. В противном случае я добавляю вес в конец вектора. Если весов в моем векторе меньше, чем указано мной max_weights (которое равно 4) - я заполняю оставшиеся веса / JointIds значением 0.