Как применить скелетную анимацию из файла .x (Direct X)?

8

Используя формат .x для экспорта модели из Blender, я могу загрузить сетку, арматуру и анимацию. У меня нет проблем с генерацией сетки и просмотром моделей в игре. Кроме того, у меня есть анимации и арматура, правильно загруженная в соответствующие структуры данных.

Моя проблема - правильно применить анимацию к моделям. У меня есть рамки для применения моделей и код для выбора анимации и пошагового просмотра кадров.

Из того, что я понимаю, AnimationKeyэлементы s внутри AnimationSetснабжают преобразования для преобразования позы связывания в позу в анимированном кадре. В качестве небольшого примера:

Animation {
  {Armature_001_Bone}
  AnimationKey { 
    2; //Position
    121; //number of frames
    0;3;     0.000000, 0.000000, 0.000000;;,
    1;3;     0.000000, 0.000000, 0.005524;;,
    2;3;     0.000000, 0.000000, 0.022217;;,
    ...
  }
  AnimationKey { 
    0; //Quaternion Rotation 
    121;
    0;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
    1;4;   -0.697332, 0.697332, 0.015710, 0.015710;;,
    2;4;   -0.684805, 0.684805, 0.035442, 0.035442;;,
    ...
  }
  AnimationKey { 
    1; //Scale
    121;
    0;3;     1.000000, 1.000000, 1.000000;;,
    1;3;     1.000000, 1.000000, 1.000000;;,
    2;3;     1.000000, 1.000000, 1.000000;;,
    ...
  }
}

Таким образом, чтобы применить кадр 2, я бы взял положение, поворот и масштаб из кадра 2, создал Transform_Aиз них матрицу преобразования (назовем ее ) и применил эту матрицу к вершинам, управляемым по Armature_001_Boneих весам. Так что я запихаю TransformAв свой шейдер и преобразую вершину. Что-то вроде:

vertexPos = vertexPos * bones[ int(bfs_BoneIndices.x) ] * bfs_BoneWeights.x;

Где bfs_BoneIndicesи bfs_BoneWeightsзначения, специфичные для текущей вершины.

При загрузке в вершины сетки я преобразую их с помощью rootTransformи meshTransform. Это гарантирует, что они ориентированы и правильно масштабированы для просмотра позы связывания.

Проблема в том, что когда я создаю эту матрицу преобразования (используя положение, вращение и масштаб из анимации), она неправильно трансформирует вершину. Скорее всего, это нечто большее, чем просто использование данных анимации. Я также попытался применить иерархию костных трансформаций, все еще без игры в кости. В основном я заканчиваю с некоторыми витыми моделями. Следует также отметить, что я работаю в openGL, поэтому следует учитывать любые преобразования матрицы, которые, возможно, потребуется применить.

Какие данные мне нужны и как их объединить для применения анимации .x к моделям?

Я сделал несколько макетов того, как это выглядит, на случай, если это будет полезно.

Сначала я хотел просто проверить перевод, это подпрыгивающая голова, как она выглядит в Blender:

http://i.stack.imgur.com/NAc4B.gif

И как это выглядит в игре (не обращайте внимания на цвета):

http://i.stack.imgur.com/nj2du.gif

Затем для простого поворота анимацией была голова, вращающаяся на 360 градусов вокруг вертикальной оси. Вот как это выглядит в игре:

http://i.stack.imgur.com/gVyUW.gif

Обратите внимание, не должно быть никакого наклона, только вращение как карусель.

Обновить

У меня работает переводная часть анимации. Но это кажется немного хакерским, и я не понимаю, как применить его к ротации.

Перевод работает, выполнив следующие действия:

  1. Возьмите позицию с кадр анимации и поменять местами yи zзначение
  2. Перевести матрицу преобразования по измененной позиции
  3. Транспонировать матрицу преобразования
  4. Применить матрицу преобразования к вершинам

Вот как это может работать, но как это должно работать в целом для положения, масштаба и поворота?

MichaelHouse
источник
2
Преобразовывали ли вы вершину с помощью матрицы обратной связывания перед применением скиннинга?
r2d2rigo
У меня есть эффект, но он нежелателен. Похоже, что это влияет на визуальное представление модели (инвертирование всех нормалей, выглядит как), но не влияет на движение / вращение / масштаб.
MichaelHouse
Не могли бы вы опубликовать скриншот, иллюстрирующий проблему?
r2d2rigo
@ r2d2rigo Я обновил анимированные картинки.
MichaelHouse
1
Здесь я в основном устраиваю мозговые штурмы, но если порядок векторов (xyz, zxy) отличается между .x и opengl, вы получите несколько забавных эффектов. Тот факт, что транспонирование помогает, означает, что он использует вектор столбца (или строки) для позиции, а не наоборот. en.wikipedia.org/wiki/… имеет описание поворотов для 2D. Я не совсем знаю, как изменить кватернион, хотя. Или разница между форматами .x и openGL
Даниэль Карлссон

Ответы:

3

Следует также отметить, что я работаю в openGL, поэтому следует учитывать любые преобразования матрицы, которые, возможно, потребуется применить.

Может быть, это ключ, вы пытались свести на нет некоторые Z-координаты, чтобы преобразовать их из левосторонних координат DirectX в правые OpenGL?

Вы можете отрицать все Zs перед применением ваших преобразований и повторно отрицать, когда закончите, посмотрите, улучшится ли результат.

РЕДАКТИРОВАТЬ

Поменять местами координаты Y и Z - это еще один способ изменить руку. Так что вам нужно адаптировать свои преобразования, проверьте эту статью для деталей .

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

Лоран Кувиду
источник
Как бы сказать мне, что я уже знаю здесь ...
MichaelHouse
1
Ну, вы не упомянули в своем вопросе двуличность, и я считаю, что это, по крайней мере, часть вашей проблемы. Я не могу предположить, что вы делаете или не знаете, что DirectX и OpenGL следуют различным соглашениям в этом вопросе (это на самом деле немного сложнее ). Обратите внимание, что это может помочь другим людям, читающим этот вопрос, увидеть его упомянутым, что является своего рода смыслом всего этого сайта.
Лоран Кувиду
Справедливо, ты прав. Я только намекнул на тот факт, что я знал о преобразованиях, которые должны были произойти, когда я упомянул об использовании OpenGL, и о транспонировании, которое должно было произойти.
MichaelHouse
2

Хорошо, я потратил немного больше времени на это и заставил его работать на несколько костей, и значительно менее похож на хак (хотя все еще немного). Система, которую я использовал, изначально была почти правильной. Однако основными проблемами были:

  1. Blender сохраняет кватернионы в форматах W, X, Y, Z. Я загружал их X, Y, Z, W .
  2. Мой код для преобразования кватерниона в матрицу был неверным. Я на самом деле все еще не уверен, почему это неверно, но я знаю, что следующий код работает, как часть моего класса Quaternion:

    public Matrix4f toMatrix() {
        Matrix4f tmpMatrix = new Matrix4f();
        if (this.length() > 0)
            this.normalise();
    
        float xx = this.x * this.x;
        float xy = this.x * this.y;
        float xz = this.x * this.z;
        float wx = this.x * this.w;
    
        float yy = this.y * this.y;
        float yz = this.y * this.z;
        float wy = this.y * this.w;
    
        float zz = this.z * this.z;
        float wz = this.z * this.w;
        //note the "* -1"s
        //I needed those to make the same matrices Blender was expecting
        tmpMatrix.m00 = (1.0f - 2.0f * (yy + zz)) * -1;
        tmpMatrix.m01 = (       2.0f * (xy - wz)) * -1;
        tmpMatrix.m02 =        2.0f * (xz + wy);
        tmpMatrix.m03 = 0;
    
        tmpMatrix.m10 =        2.0f * (xy + wz);
        tmpMatrix.m11 = 1.0f - 2.0f * (xx + zz);
        tmpMatrix.m12 = (       2.0f * (yz - wx)) * -1;
        tmpMatrix.m13 = 0;
    
        tmpMatrix.m20 =        2.0f * (xz - wy);
        tmpMatrix.m21 =        2.0f * (yz + wx);
        tmpMatrix.m22 = (1.0f - 2.0f * (xx + yy)) * -1;
        tmpMatrix.m23 = 0;
        return tmpMatrix;
    }

Кроме этого, это просто:

finalMatrix = boneMatrix * skinMatrix * invertedBindMatrix;

Обратите внимание, что boneMatrixдля каждой кости следует использовать анимированные матрицы вместо матриц связывания. Он также должен получить все свои родительские матрицы следующим образом:

private Matrix4f getBoneMatrix() {
        if (parent == null) {
            return Matrix4f.mul(Matrix4f.mul(parentArmature.getBindMatrix(), parentArmature.getArmatureRootMatrix(), null), boneMatrix, null);
        } else {
            return Matrix4f.mul(parent.getBoneMatrix(), boneMatrix, null);
        }
    }

И это работает. Оставьте комментарий, если вам нужны разъяснения.

MichaelHouse
источник
Я написал более общую информацию о том, как заставить это работать, в моем блоге: byte56.com/2012/12/the-blender-connection.html
MichaelHouse