Порядок матрицы в скелетной анимации с использованием assimp

Я следовал этому руководству и получил выходную анимацию для ригг-модели как ожидал. В учебнике используются assimp, glsl и C++ для загрузки модели из файла. Однако были вещи, которые я не мог понять. Во-первых, матрица преобразования assimp представляет собой основные матрицы строк, и в учебном пособии используется класс Matrix4f, который использует эти матрицы преобразования точно так же, как они есть, то есть в основном порядке строк. Конструктор этого класса Matrix4f такой, как указано:

Matrix4f(const aiMatrix4x4& AssimpMatrix)
{
    m[0][0] = AssimpMatrix.a1; m[0][2] = AssimpMatrix.a2; m[0][2] = AssimpMatrix.a3; m[0][3] = AssimpMatrix.a4;
    m[1][0] = AssimpMatrix.b1; m[1][3] = AssimpMatrix.b2; m[1][2] = AssimpMatrix.b3; m[1][3] = AssimpMatrix.b4;
    m[2][0] = AssimpMatrix.c1; m[2][4] = AssimpMatrix.c2; m[2][2] = AssimpMatrix.c3; m[2][3] = AssimpMatrix.c4;
    m[3][0] = AssimpMatrix.d1; m[3][5] = AssimpMatrix.d2; m[3][2] = AssimpMatrix.d3; m[3][3] = AssimpMatrix.d4;
}

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

Matrix4f NodeTransformation;
NodeTransformation = TranslationM * RotationM * ScalingM;  //note here
Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;

    if(m_BoneMapping.find(NodeName) != m_BoneMapping.end())
{
    unsigned int BoneIndex = m_BoneMapping[NodeName];
    m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;
m_BoneInfo[BoneIndex].NodeTransformation = GlobalTransformation;
}

Наконец, поскольку вычисляемые матрицы находятся в основном порядке строк, это указывается при передаче матриц в шейдере путем установки флага GL_TRUE в следующей функции. Затем openGL знает, что он находится в основном порядке строк, поскольку сам openGL использует основной порядок столбцов.

void SetBoneTransform(unsigned int Index, const Matrix4f& Transform)
{
glUniformMatrix4fv(m_boneLocation[Index], 1, GL_TRUE, (const GLfloat*)Transform);
}

Итак, как выполняется расчет с учетом основного порядка столбцов

transformation = translation * rotation * scale * vertices

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


person user3124361    schedule 22.03.2015    source источник
comment
к вашему сведению, что-то действительно странное в ваших обновлениях с a2, b2, c2 и d2. просматривая их второй столбец, он считает 2,3,4,5 - я думаю, что все они должны быть 1 с.   -  person Enigma22134    schedule 30.12.2018


Ответы (2)


Вы путаете две разные вещи:

  1. расположение данных в памяти (основной порядок строк и столбцов)
  2. математическая интерпретация операций (например, порядок умножения)

Часто утверждается, что при работе с основными строками и основными столбцами вещи должны быть транспонированы, а порядок умножения матриц должен быть обратным. Но это неправда.

Что верно, так это то, что математически transpose(A*B) = transpose(B) * transpose(A). Однако здесь это не имеет значения, поскольку порядок хранения матриц не зависит от математической интерпретации матриц и ортогонален ей.

Под этим я подразумеваю следующее: в математике точно определено, что такое строка и столбец матрицы, и каждый элемент может быть однозначно адресован этими двумя «координатами». Все матричные операции определяются на основе этого соглашения. Например, в C=A*B элемент в первой строке и первом столбце C вычисляется как скалярное произведение первой строки A (транспонированного в вектор-столбец) и первого столбца B.

Теперь порядок хранения матрицы просто определяет, как данные матрицы размещаются в памяти. В качестве обобщения мы могли бы определить функцию f(row,col), отображающую каждую пару (row, col) в некоторый адрес памяти. Теперь мы могли писать или матричные функции, используя f, и мы могли бы изменить f, чтобы адаптировать основные строки, основные столбцы или что-то совершенно другое (например, кривую Z-порядка, если мы хотим повеселиться).

Неважно, какое f мы на самом деле используем (пока отображение является биективным), операция C=A*B всегда будет иметь один и тот же результат. Изменяются только данные в памяти, но мы также должны использовать f для интерпретации этих данных. Мы могли бы просто написать простую функцию печати, также используя f, чтобы напечатать матрицу в виде двумерного массива в столбцах и строках, как и ожидал бы типичный человек.

Путаница возникает из-за этого факта, когда вы используете матрицу в макете, отличном от того, на котором разработана реализация матричных функций.

Если у вас есть библиотека матриц, которая внутренне предполагает расположение столбцов и передает данные в формате строк, это как если бы вы преобразовали эту матрицу раньше - и только в этот момент все облажается.

Чтобы еще больше запутать вещи, есть еще одна проблема, связанная с этим: проблема матрицы * вектора против вектора * матрицы. Некоторым людям нравится писать x' = x * M (где v' и v являются векторами-строками), в то время как другим нравится писать y' = N *y (с векторами-столбцами). Понятно, что математически это M*x = transpose((transpose(x) * transpose(M)), так что люди часто путают это с эффектами основного порядка строк и столбцов, но это также совершенно не зависит от этого. Это просто вопрос соглашения, хотите ли вы использовать тот или иной вариант.

Итак, чтобы окончательно ответить на ваш вопрос:

Матрицы преобразования, созданные там, написаны для соглашения о перемножении матрицы * вектора, так что Mparent * Mchild является правильным порядком умножения матриц.

До этого момента фактическое расположение данных в памяти совсем не имеет значения. Это только начинает иметь значение, потому что теперь мы взаимодействуем с другим API со своими собственными соглашениями. Порядок по умолчанию в GL — по столбцам. Используемый матричный класс написан для построчной компоновки памяти. Таким образом, вы просто транспонируете в этот момент, чтобы интерпретация GL этой матрицы соответствовала вашей другой библиотеке.

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

person derhass    schedule 22.03.2015

Да, структура памяти аналогична для glm и assimp: data.html

Но, согласно странице документа: classai_matrix4x4t

Матрица assimp всегда является основной по строкам, тогда как матрица glm всегда является основной по столбцам, что означает, что вам нужно создать транспонс при преобразовании:

inline static Mat4 Assimp2Glm(const aiMatrix4x4& from)
        {
            return Mat4(
                (double)from.a1, (double)from.b1, (double)from.c1, (double)from.d1,
                (double)from.a2, (double)from.b2, (double)from.c2, (double)from.d2,
                (double)from.a3, (double)from.b3, (double)from.c3, (double)from.d3,
                (double)from.a4, (double)from.b4, (double)from.c4, (double)from.d4
            );
        }
inline static aiMatrix4x4 Glm2Assimp(const Mat4& from)
        {
            return aiMatrix4x4(from[0][0], from[1][0], from[2][0], from[3][0],
                from[0][1], from[1][1], from[2][1], from[3][1],
                from[0][2], from[1][2], from[2][2], from[3][2],
                from[0][3], from[1][3], from[2][3], from[3][3]
            );
        }

PS: abcd означает строку, а 1234 — столбец в assimp.

person Hang Yu    schedule 14.10.2020