Матричные расчеты для скиннинга GPU
Я пытаюсь сделать скелетную анимацию в OpenGL, используя Assimp в качестве моей библиотеки импорта моделей.
Что именно мне нужно с костями? offsetMatrix
переменная? На что мне нужно умножить это?
3 ответа
Давайте возьмем, к примеру, этот код, который я использовал для анимации персонажей в игре, над которой я работал. Я также использовал Assimp, чтобы загрузить информацию о костях, и я прочитал учебник по OGL, на который уже указывал Нико.
glm::mat4 getParentTransform()
{
if (this->parent)
return parent->nodeTransform;
else
return glm::mat4(1.0f);
}
void updateSkeleton(Bone* bone = NULL)
{
bone->nodeTransform = bone->getParentTransform() // This retrieve the transformation one level above in the tree
* bone->transform //bone->transform is the assimp matrix assimp_node->mTransformation
* bone->localTransform; //this is your T * R matrix
bone->finalTransform = inverseGlobal // which is scene->mRootNode->mTransformation from assimp
* bone->nodeTransform //defined above
* bone->boneOffset; //which is ai_mesh->mBones[i]->mOffsetMatrix
for (int i = 0; i < bone->children.size(); i++) {
updateSkeleton (&bone->children[i]);
}
}
По сути GlobalTransform
как это указано в учебном пособии по скелетной анимации с Assimp или правильно преобразование корневого узла scene->mRootNode->mTransformation
это преобразование из локального пространства в глобальное пространство. Например, когда в 3D-моделере (например, давайте выберем Blender) вы создаете свой меш или загружаете своего персонажа, он обычно располагается (по умолчанию) в начале декартовой плоскости, а его вращение устанавливается на личность кватерниона.
Однако вы можете перевести / повернуть вашу сетку / персонажа от начала координат (0,0,0)
куда-то еще и иметь в одной сцене даже несколько сеток с разных позиций. Когда вы загружаете их, особенно если вы делаете скелетную анимацию, их обязательно нужно переводить обратно в локальное пространство (то есть обратно в начало координат). 0,0,0
) и это причина, почему вы должны умножить все на InverseGlobal
(который возвращает вашу сетку в локальное пространство).
После этого вам нужно умножить его на преобразование узла, которое является умножением parentTransform
(преобразование на один уровень выше в дереве, это общее преобразование) transform
(ранее assimp_node->mTransformation
это просто преобразование кости относительно родителя узла) и ваше локальное преобразование (любое T * R), которое вы хотите применить: прямая кинематическая, обратная кинематическая или ключевая интерполяция.
В конце концов есть boneOffset (ai_mesh->mBones[i]->mOffsetMatrix
), который трансформируется из пространства сетки в пространство кости в позе связывания, как указано в документации.
Здесь есть ссылка на GitHub, если вы хотите посмотреть весь код моего класса Skeleton.
Надеюсь, поможет.
Матрица смещения определяет преобразование (перемещение, масштабирование, вращение), которое преобразует вершину в пространстве сетки и преобразует ее в пространство "кости". В качестве примера рассмотрим следующую вершину и кость со следующими свойствами;
Vertex Position<0, 1, 2>
Bone Position<10, 2, 4>
Bone Rotation<0,0,0,1> // Note - no rotation
Bone Scale<1, 1, 1>
Если мы умножим вершину на Матрицу смещения в этом случае, мы получим положение вершины <-10, -1, 2>.
Как мы это используем? У вас есть два варианта использования этой матрицы, в зависимости от того, как мы храним данные вершин в буферах вершин. Варианты есть;
1) Храните вершины сетки в пространстве сетки 2) Храните вершины сетки в пространстве кости
В случае #1 мы бы взяли offset Matrix и применили его к вершинам, на которые воздействует кость, когда мы строим буфер вершин. И затем, когда мы анимируем меш, мы позже применяем анимированную матрицу для этой кости.
В случае #2 мы будем использовать offset Matrix в сочетании с матрицей анимации для этой кости при преобразовании вершин, хранящихся в буфере вершин. Так что это будет что-то вроде (Примечание: вам, возможно, придется переключать конкатенации матрицы здесь);
anim_vertex = (offset_matrix * anim_matrix) * mesh_vertex
Это помогает?
Как я уже предполагал, mOffsetMatrix
является обратной матрицей позы связывания. В этом уроке указаны правильные преобразования, необходимые для линейного слияния:
Сначала вам нужно оценить состояние анимации. Это даст вам системное преобразование из пространства анимированных костей в мировое пространство для каждой кости (GlobalTransformation
в учебнике). mOffsetMatrix
это система трансформации из мирового пространства в связывающее позу костного пространства. Поэтому, что вы делаете для снятия скинов, это следующее (при условии, что конкретная вершина находится под влиянием одной кости): Преобразование вершины в пространство кости с помощью mOffsetMatrix
, Теперь примите анимированную кость и трансформируйте промежуточный результат обратно из пространства анимированной кости в мировое пространство. Так:
boneMatrix[i] = animationMatrix[i] * mOffsetMatrix[i]
Если на вершину влияют несколько костей, LBS просто усредняет результаты. Вот где веса вступают в игру. Скиннинг обычно реализуется в вершинном шейдере:
vec4 result = vec4(0);
for each influencing bone i
result += weight[i] * boneMatrix[i] * vertexPos;
Обычно максимальное количество влияющих костей фиксировано, и вы можете развернуть for
петля.
Учебник использует дополнительный m_GlobalInverseTransform
для boneMatrix
, Тем не менее, я понятия не имею, почему они это делают. По сути, это отменяет общую трансформацию всей сцены. Вероятно, он используется для центрирования модели в представлении.