#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, и вместо этого возьму обратное локальное преобразование родительского соединения, умноженное на локальное преобразование соединения, я получаю следующее:
На этом скриншоте я рисую линию от каждой вершины до соединений, которые оказывают влияние. Тот факт, что я получаю позицию привязки, не так уж и странно, потому что формула теперь становится:
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 );