Войти
ПрограммированиеСтатьиГрафика

Скелетная анимация и экспорт из Maya 4.0. (3 стр)

Автор:

Применение анимационной математики в экспорте

Теперь о том, как получить скелет с привязкой кожи и его анимационный трек.

Во-первых я строил два 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'а, влияющего на нее.

Страницы: 1 2 3 4 Следующая »

#Maya, #анимация, #скелетная анимация, #экспорт

6 марта 2003 (Обновление: 23 сен. 2009)

Комментарии [1]