Анимация снятия шкур - сетка разрушения веса - PullRequest
0 голосов
/ 20 июня 2020

Я нахожусь в процессе написания системы анимации со своим собственным парсером Collada и столкнулся с проблемой, которую не могу понять.

Я собрал информацию о своей сетке / скине (вершины, нормали, JointIds, веса, и т. д. c) , информация о моем скелете (суставы, их локальные преобразования, обратная позиция привязки, структура иерархии) и моя анимация ( позиция преобразования ключевого кадра для каждого сустава, временная метка) .

Моя проблема в том, что когда все вычисляется и затем реализуется в шейдере (сумма весов, умноженная на совместное преобразование и положение вершины), я получаю следующее:

enter image description here

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:

  1. 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);
        }
    }
  1. В моем аниматоре во время анимации для каждого сустава я беру два 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.

1 Ответ

0 голосов
/ 28 июня 2020

Я понимаю, что когда что-то идет не так с анимацией скинов, может быть много разных областей, в которых возникает проблема. Таким образом, будущие гуглеры, столкнувшиеся с той же проблемой, что и я, - воспринимайте это как скорее список предложений о том, что вы могли делать неправильно, а не абсолютно делаете неправильно.

Что касается моей проблемы - у меня была правильная идея, но неправильный подход во многих второстепенных областях. Это меня довольно близко приблизило, но, как говорится, сигары не было.

  1. Мне не нужно было самому рассчитывать Позу Обратной Связи, Позу Обратной Связи Коллады (иногда / часто декларируемую как " offsetMatrix ") более чем идеален. Это не было проблемой, поскольку я просто делал ненужные вычисления.

  2. В файле Collada они часто предоставляют вам больше «стыков» или «узлов» в иерархии, чем то, что есть необходимо для анимации. Перед началом ваших реальных анимированных «суставов» существует сцена и начальный тип «узла» арматуры. Сцена обычно представляет собой матрицу идентичности, которой манипулируют на основе вашей «оси вверх» при чтении файла Collada. Тип узла будет определять общий размер каждого сустава в скелете, поэтому, если он не был изменен, вероятно, это матрица идентичности. Убедитесь, что ваша иерархия по-прежнему содержит ВСЕ узлов / соединений, перечисленных в иерархии. Я очень сильно не делал это, что сильно исказило мою globalPosition (BindPose).

    введите описание изображения здесь

  3. Если вы представляете вращение преобразований вашего Joint через кватернионы (что настоятельно рекомендуется), убедитесь, что полученный кватернион нормализован после интерполяции между двумя повернутыми позициями. В том же примечании - при объединении вращения и преобразования в вашу окончательную матрицу - убедитесь, что ваш порядок умножения и окончательный результат верны.

  4. Наконец, ваша последняя матрица скинов состоит из ваши суставы InvBindMatrix * GlobalPosition * GlobalInverseRootTransform (<- это обратная локальная трансформация из вашего узла "сцены", упомянутого в (1), помните?). Основываясь на ваших предыдущих умножениях матриц до этого момента, вам может потребоваться или не потребоваться транспонировать эту окончательную матрицу. </p>

И с этим - я смог успешно оживить свою модель! *

И последнее замечание - мой me sh и файлы анимации добавляются отдельно. Если ваши анимации находятся в отдельных файлах от вашего me sh, убедитесь, что вы собрали информацию о скинах / соединениях из файлов с анимацией, а не из файла с me sh. Я перечисляю свои шаги по загрузке модели, а затем добавляю ей несколько анимаций через разные файлы:

  1. Загрузить в Me sh (Он содержит вершины, нормали, TexCoords, JointIds, веса)
  2. Загрузить файл анимации (это дает Skeleton, InverseBindPositions и другую необходимую информацию для привязки скелета ко мне sh) - Как только информация о скелете и привязке собрана, соберите также первую информацию об анимации из этого файла.
  3. Для другой анимации приведенный выше скелет должен нормально работать для любой другой анимации на той же сетке / модели - просто прочтите информацию об анимации и сохраните в выбранной вами структуре данных. Повторяйте шаг 3, пока не будете довольны.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...