Скелетная анимация и экспорт из Maya 4.0. (3 стр)
Автор: Aidan
Применение анимационной математики в экспорте
Теперь о том, как получить скелет с привязкой кожи и его анимационный трек.
Во-первых я строил два plug-in'а - отдельно для экспорта скелета и кожи и отдельно для экспорта анимации. Однако и в том и в другом случае необходимо построить скелет. Для этого необходимо
1. Найти корневой joint в сцене
2. Найти все joint'ы, присоединенные к нему
3. Определить преобразования каждого joint'а относительно "родителя"
Находим корневой joint.
В Maya существует два дерева - для DAG объектов и для Dependency объектов. И для тех и для других существует свой итератор ("переборщик"). Я использую его для поиска всех объектов, а затем выделяю только те, у которых присутствует набор необходимых функций (в данном случае необходимы объекты, с которыми можно работать как с joint'ами).
MObject searchRootJoint() { // создать итератор MItDag itDag; // перебирать все объекты, пока не закончатся for( ; !itDag.isDone( ); itDag.next( )) { // для начала выбираем все объекты, которые являются // началом для каждого из возможных поддеревьев Maya if( itDag.depth( ) != 1) continue; // получаем объект (в представлении Maya) MObject object = itDag.item( ); // если у объекта нет набора функций для работы с joint'ами, // пытаемся найти такой среди его "потомков" if( !object.hasFn( MFn::kJoint)) { // определяем узел DAG-дерева MFnDagNode fnNode( object); MDagPath dagPath; // получаем путь к нему (в представлении Maya) fnNode.getPath( dagPath); // пытаемся искать (это моя функция, которая проверяет всех // потомков данного объекта на наличие функций для joint'ов) object = traversDagForJoints( dagPath); if( !object.isNull( )) return object; } // иначе, такой объект найден else return object; } // не найдено ни одного объекта с нужным набором функций return MObject::kNullObj; }
Следующие два пункта обычно идут вместе. То есть, зная корневой joint, собираем всех его потомков с параллельным вычислением относительных преобразований. Для списка joint'ов нам необходимы:
a) его поворот относительно "родительского" joint'а (кватернион)
b) перемещение относительно "родительского" joint'а
c) масштабирование относительно "родительского" joint'а
d) индекс "родительского" joint'а
e) текущая матрица преобразований
Первый joint в списке никак не относиться к Maya. Это "мертвый" joint, без каких-нибудь преобразований, добавленный для упрощения расчетов.
// матрица преобразований - единичная jointsArray[0].current.setToIdentity(); // кватернион равен (0 0 0 1) - нулевой поворот вокруг любой оси jointsArray[0].quat[0] = 0.0f; jointsArray[0].quat[1] = 0.0f; jointsArray[0].quat[2] = 0.0f; jointsArray[0].quat[3] = 1.0f; // переноса нет ("мертвый" joint находиться в центре координат) jointsArray[0].translate[0] = 0.0f; jointsArray[0].translate[1] = 0.0f; jointsArray[0].translate[2] = 0.0f; jointsArray[0].translate[3] = 0.0f; // нет изменения в размерах jointsArray[0].scale[0] = 1.0f; jointsArray[0].scale[1] = 1.0f; jointsArray[0].scale[2] = 1.0f; jointsArray[0].scale[3] = 1.0f; // с "мертвым" joint'ом ассоциирован пустой объект (представление Maya) jointsArray[0].object = new MObject( MObject::kNullObj); // у него нет родителя jointsArray[0].parent = 0xFFFFFFFF;
Далее, как и при поиске корневого joint'а, проходим по всем потомкам и добавляем к нашему списку только объекты, отмеченные как joint'ы.
А вот и математика :)
Выше я говорил только о получении текущих преобразований, зная "родительские" и относительные. Сейчас необходимо получить относительные преобразования.
Cr=Pr*Rr
Ct=Pt+Pr*Rt
Решая эти два уравнения относительно Rr и Rt получаем (опять-таки матрица умножается на вектор)
Rr=Pr-1*Cr
Rt=Pr-1*(Ct-Pt)
где Cr - текущая матрица поворота, Pr - "родительская" матрица поворота, Rr - матрица относительного поворота Pt - "родительский" вектор перемещения Pr-1 - обратная "родительская" матрица поворота, Rt - вектор относительного перемещения
// эти матрицы нужны для получения информации о преобразованиях joint'ов MTransformationMatrix parentTransform, relativeTransform, currentTransform; // масштабирование double scale[3]; // вектора перемещений MVector parentTranslate, currentTranslate, relativeTranslate; // матрица поворотов MMatrix parentRotate, currentRotate, relativeRotate; // матрица "родительских" преобразований parentTransform = jointsArray[parent].current; // получить "родительское" перемещение (параметр MSpace::kWorld определяет, что // полученные координаты будут в заданы в мировой системе координат) parentTranslate = parentTransform.translation(MSpace::kWorld); // получить "родительский" поворот parentRotate = parentTransform.asRotateMatrix( ); // текущие преобразования (для DAG-объектов матрицу преобразований в мировой // системе координат можно получить, используя путь к ней в представлении Maya) currentTransform = dagPath.inclusiveMatrix( ); // текущее перемещение currentTranslate = currentTransform.translation( MSpace::kWorld); // текущее вращение currentRotate = currentTransform.asRotateMatrix( ); // относительное перемещение relativeTranslate = parentRotate.inverse( ) * ( currentTranslate - parentTranslate); // относительный поворот relativeRotate = parentRotate.inverse( ) * currentRotate; // это присваивание необходимо для получения данных о масштабировании и повороте, // чтобы не вытаскивать их вручную relativeTransform = relativeRotate; // переводим в кватернион MQuaternion quaternion = relativeTransform.eulerRotation( ).asQuaternion( ); // получаем масштабирование relativeTransform.getScale( scale, MSpace::kWorld); // заполняем данные // индекс родителя jointsArray[id].parent = parent; // объект joint'а (в представлении Maya) jointsArray[id].object = new MObject( newJoint); // имя strncpy( ( char *)&jointsArray[id].name, joint.name( ).asChar( ), 31); // текущая матрица преобразований jointsArray[id].current = currentTransform.asMatrix( ); // поворот (кватернион) jointsArray[id].quat[0] = ( float)quaternion.x; jointsArray[id].quat[1] = ( float)quaternion.y; jointsArray[id].quat[2] = ( float)quaternion.z; jointsArray[id].quat[3] = ( float)quaternion.w; // перенос jointsArray[id].translate[0] = ( float)relativeTranslate.x; jointsArray[id].translate[1] = ( float)relativeTranslate.y; jointsArray[id].translate[2] = ( float)relativeTranslate.z; jointsArray[id].translate[3] = 0; // масштабирование jointsArray[id].scale[0] = ( float)scale[0]; jointsArray[id].scale[1] = ( float)scale[1]; jointsArray[id].scale[2] = ( float)scale[2]; jointsArray[id].scale[3] = 1;
Дальше, для анимации необходимо определить положения скелете в каждом кадре. Для этого:
1. Получаем число кадров. Для этого необходим объект MAnimControl и MTime. Второй устанавливает время для нужного кадра, а первый, по установленному времени устанавливает скелет в позу, заданную номером кадром
2. Собираем скелет в каждом кадре и записываем положение joint'ов
Все :)
Чтобы собрать скелет вместе с кожей нужно немного больше усилий.
Для начала необходимо, чтобы скелет находился в позе первичной привязки - "bind pose". В этом случае все вершины находятся на своих местах и нет растяжения или сжатия полигонов/
Пример клоуна в "bind pose"

Из plug-in'а это можно сделать 3 командами MEL
select -cl;
select -r Root; // предположим корневой joint назван Root
dagPose -restore -global -bindPose;Далее необходимо найти меш, на который влияет собранный скелет. Для этого необходим "Skin Cluster". Он определяет то, какие вершины к какому joint'у и с каким весом привязаны - все что необходимо. Для одного скелета существует только один "Skin Cluster". Его поиск производиться точно также как и поиск корневого joint'а, с той лишь разницей, что используется фильтр kSkinClusterFilter, и "Skin Cluster" не является DAG-объектом, поэтому необходим итератор MItDependencyNodes.
Теперь по найденному "Skin Cluster" находим меш (точнее путь к нему в представлении Maya)
// для "Skin Cluster" существует специальный класс MFnSkinCluster // в принципе скелет может влиять на несколько мешей, я остановлюсь на одном // получить индекс первого меша в массиве данных "Skin Cluster" unsigned int index = skinCluster.indexForOutputConnection(0, &stat); // получить путь к мешу в представлении Maya skinCluster.getPathAtIndex( index, skinPath);
А дальше проходим по всем вершинам меша и определяем какие joint'ы на них влияют, при этом параллельно пересчитываем координаты вершин их в системе координат каждого joint'а, влияющего на нее.
#Maya, #анимация, #скелетная анимация, #экспорт
6 марта 2003 (Обновление: 23 сен 2009)
Комментарии [1]