COLLADA: положение обратной привязки в неправильном пространстве?

#c #math #matrix #skinning #collada

#c #математика #матрица #скининг #collada

Вопрос:

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

Формула, которую я использую для очистки моих сеток, проста:

 weighted;
for (i = 0; i < joint_influences; i  )
{
    weighted  = 
        joint[joint_index[i]]->parent->local_matrix * 
        joint[joint_index[i]]->local_matrix * 
        skin->inverse_bind_pose[joint_index[i]] * 
        position * 
        skin->weight[j];
}
position = weighted;
  

И что касается литературы, это правильная формула. Теперь COLLADA определяет два типа поворотов для соединений: локальный и глобальный. Вы должны объединить вращения вместе, чтобы получить локальное преобразование для соединения.

Документация COLLADA не делает различий между локальным вращением соединения и глобальным вращением соединения. Но в большинстве моделей, которые я видел, вращения могут иметь идентификатор либо rotate (глобальный), либо jointOrient (локальный).

Когда я игнорирую глобальные вращения и использую только локальные, я получаю положение привязки для модели. Но когда я добавляю глобальные вращения к локальному преобразованию соединения, начинают происходить странные вещи.

Это без использования глобальных поворотов:

Положение привязки

И это с глобальными поворотами:

Странно

На обоих скриншотах я рисую скелет с помощью линий, но на первом он невидим, потому что соединения находятся внутри сетки. На втором скриншоте вершины повсюду!

Для сравнения, вот как должен выглядеть второй скриншот:

Средство просмотра Collada

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

Но теперь странная вещь. Если я проигнорирую положение обратной привязки, указанное COLLADA, и вместо этого возьму обратное локальное преобразование родительского соединения, умноженное на локальное преобразование соединения, я получаю следующее:

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

На этом скриншоте я рисую линию от каждой вершины до соединений, которые оказывают влияние. Тот факт, что я получаю позицию привязки, не так уж и странно, потому что формула теперь становится:

 world_matrix * inverse_world_matrix * position * weight
  

Но это заставляет меня подозревать, что обратная позиция привязки COLLADA находится в неправильном пространстве.

Итак, мой вопрос: в каком пространстве COLLADA указывает свою позицию обратной привязки? И как я могу преобразовать положение обратной привязки в нужное мне пространство?

Комментарии:

1. Вы читали раздел «Снятие оболочки с скелета в COLLADA» спецификации 1.4.1? Ваша формула выглядит не так

Ответ №1:

Я начал с сравнения своих значений с теми, которые я прочитал из Assimp (загрузчик моделей с открытым исходным кодом). Просматривая код, я посмотрел, где они построили свои матрицы привязки и их обратные матрицы привязки.

В конце концов я оказался в SceneAnimator::GetBoneMatrices , который содержит следующее:

 // Bone matrices transform from mesh coordinates in bind pose to mesh coordinates in skinned pose
// Therefore the formula is offsetMatrix * currentGlobalTransform * inverseCurrentMeshTransform
for( size_t a = 0; a < mesh->mNumBones;   a)
{
    const aiBone* bone = mesh->mBones[a];
    const aiMatrix4x4amp; currentGlobalTransform
        = GetGlobalTransform( mBoneNodesByName[ bone->mName.data ]);
    mTransforms[a] = globalInverseMeshTransform * currentGlobalTransform * bone->mOffsetMatrix;
}
  

globalInverseMeshTransform всегда является идентификатором, потому что сетка ничего не преобразует. currentGlobalTransform объединена ли матрица привязки, локальные матрицы родительского соединения с локальной матрицей соединения. И mOffsetMatrix это матрица обратной привязки, которая поступает непосредственно из оболочки.

Я сверил значения этих матриц со своими собственными (о, да, я сравнил их в окне просмотра), и они были точно такими же, возможно, на 0,0001%, но это незначительно. Итак, почему версия Assimp работает, а моя нет, хотя формула одинакова?

Вот что я получил:

Перевернуто!

Когда Assimp, наконец, загружает матрицы в шейдер скининга, они выполняют следующее:

 helper->piEffect->SetMatrixTransposeArray( "gBoneMatrix", (D3DXMATRIX*)matrices, 60);
  

Подождите секунду. Они загружают их транспонированными? Это не может быть так просто. Ни в коем случае.

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

Ага.

Что-то еще, что я делал неправильно: я преобразовывал координаты в правильную систему (сантиметры в метры) перед применением матриц скининга. Это приводит к полностью искаженным моделям, поскольку матрицы разработаны для исходной системы координат.

БУДУЩИЕ ПОЛЬЗОВАТЕЛИ GOOGLE

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

Готово!

Комментарии:

1. Старый вопрос, но очень полезный! Большое вам спасибо!

Ответ №2:

Кстати, если вы транспонируете матрицы при их загрузке, а не транспонируете матрицу в конце (что может быть проблематично при анимации), вы хотите выполнить свое умножение по-другому (метод, который вы используете выше, по-видимому, предназначен для использования скининга в DirectX при использовании матриц, дружественных к OpenGL, — следовательно, транспонирование.)

В DirectX я транспонирую матрицы, когда они загружаются из файла, а затем использую (в приведенном ниже примере я просто применяю положение привязки для простоты):

XMMATRIX l_oWorldMatrix = XMMatrixMultiply( l_oBindPose, in_oParentWorldMatrix );

XMMATRIX l_oMatrixPallette = XMMatrixMultiply( l_oInverseBindPose, l_oWorldMatrix );

XMMATRIX l_oFinalMatrix = XMMatrixMultiply( l_oBindShapeMatrix, l_oMatrixPallette );