Нюансы при экспорте из 3DS MAX

Статья опубликована на сайте gamedev.ru 21.0.2006

Статья заняла первое место в конкурсе “Экспорт из 3DS MAX” на gamedev.ru 21.0.2006 

Вступление

Поскольку эта статья родилась из комментария к статье “Заметки о разработке плагина для экспорта геометрии и ещё чуть-чуть”  Glorg’a,  пожалуйста начните чтение с оригинальной статьи [1],  а я обойдусь без вступления 🙂

Вкратце, в статье пойдет речь о нюансах, с которыми вы (хотите или нет) обязательно столкнётесь при написании плагина экспорта из 3DS MAX.

Система координат

Обратите внимание, как ориентированы оси системы координат в 3DS MAX, и сравните системой, используемой в DirectX.

Рисунок 1. Системы координат 3DS MAX и DirectX.

Мало того, что вверх направлена ось Z, а не Y, так в 3DS MAX еще и используется правосторонняя (right-handed), а не левосторонняя (left-handed) система координат, как в DirectX.

Если этого не учитывать, отэкспортченный персонаж в игре будет лежать на полу.

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

Для того чтобы не путаться, проще всего при экспорте поменять местами оси Y и Z. Таким образом, если в игре персонаж должен бежать вдоль оси Z, то аниматор в 3DS MAX должен анимировать его вдоль оси Y – очень простое правило.

Для того чтобы поменять оси местами, нужно просто поменять местами компоненты Y и Z в позициях вершин и нормалях. Но при этом при экспорте анимации, каждую матрицу 3DS MAX необходимо умножить спереди и сзади на матрицу перемены осей (), чтобы вернуть позицию обратно в систему координат 3DS MAX для анимации, а потом опять в нашу систему координат.

Матрица перемены осей выглядит следующим образом:

При инвертировании, матрица не изменяется.

Полностью все выглядит так:

Матрицы объекта и узла (Node)

В документации к 3DS MAX есть раздел “Must Read Sections by Plug-In Type”. Так вот, его действительно нужно прочитать! В частности, там объясняется, что такое матрица узла (node) и объекта (object).

Вся сцена в 3DS MAX представлена иерархией узлов. Когда мы создаем кубик (box) – мы создаем узел (INode) и объект (IObject), на который этот узел ссылается.

Центр локальной системы координат объекта (pivot), который мы видим в интерфейсе 3DS MAX – это визуальное представление матрицы узла. Матрицу узла можно получить методом INode->GetNodeTM().

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

Рисунок 2. Включение режима преобразования pivot’а.

Когда мы двигаем pivot, 3DS MAX изменяет одновременно и матрицу узла, и матрицу объекта так, чтобы визуально геометрия оставалась на том же месте.

 Полная формула преобразования геометрии в мировое пространство выглядит так:

Напрямую получить матрицу объекта невозможно. Можно получить матрицу преобразования геометрии в мировое пространство (INode->GetObjectTM()), и помножить ее справа на инвертированную матрицу узла.

Non-uniform scale

Non-uniform scale(масштабирование с разными коэффициентами по осям, NUS) – это зло. Это настолько большое зло, что в ранних версиях 3DS MAX при включении этого режима выскакивало предупреждение. Но в последний версиях 3DS MAX его убрали (наверное потому, что художники его все равно не читали :).

NUS рождает две следующие проблемы: 

1. Для преобразования нормалей необходимо использовать inverse transpose матрицу.

Обычно, в играх для преобразования нормалей используется та же матрица, что и для преобразования позиций, только с обнуленным смещением. В случае с NUS это не работает:

Рисунок 3. Если для преобразования нормалей использовать ту же матрицу, что и для позиций, нормали больше не перпендикулярны поверхности.

 Если для преобразования позиций используется матрица M, то для преобразования нормалей необходимо использовать . Впрочем, можно просто считать нормали уже после преобразования позиций.

Обычно проблему NUS можно решить путем выделения из матриц масштабирования, и применения его к координатам вершин (таким образом удается избавиться от масштабирования вообще). К сожалению, это не избавляет от второй, фундаментальной проблемы.

 2. Если к кости применен NUS, то при вращении она сжимается.

Вообще-то, до того, как модель дошла до стадии экспорта, проблемы уже должны были возникнуть у аниматоров, и они обязаны были вернуть модель назад моделеру :).

Поскольку в матрице узла содержится неравномерное масштабирование, то при вращении кости она описывает эллипс, а не круг. Поскольку в играх анимация кости сохраняется только в виде положения и вращения (кватернионы), то анимация в игре не будет соответствовать анимации в 3DS MAX.

Другими словами, NUS – это зло, и ваши художники должны следить, чтобы матрицы узлов не содержали его. Если нужно выполнить неравномерное сжатие, его нужно выполнять над вершинами геометрии (vertices sub-object), а не над узлом.

Матрицы также могут содержать искривление осей (переход к неортогональной системе координат). К счастью, добиться этого стандартными методами 3DS MAX очень сложно, поэтому такие случаи редки (используйте метод Matrix3->Orthogonalize()).

Отрицательное масштабирование

 3DS MAX позволяет указывать отрицательное масштабирование.

Рисунок 4. Отрицательное масштабирование.

Эффективно это масштабирование+отражение по одной из осей. Если объект отражен или отмасштабирован с отрицательным коэффициентом, то нужно поменять порядок следования вершин в треугольнике (отразить нормаль). Проверить это можно, проанализировав матрицу:


1
2
3
4
5
bool RF2_Export_FaceNeedFlip(INode *Node, float time)
{
 Matrix3 m = (Node->GetObjectTM (time));
 return (DotProd(CrossProd(m.GetRow(0),m.GetRow(1)),m.GetRow(2))<0.0)?1:0;
};

Спрятанные объекты

Иногда проблемы возникают из-за незнания интерфейса 3DS MAX. На самом деле, вы – программист, и знать 3DS Max не обязаны, но все же…

3DS MAX позволяет прятать (hide) объекты. Это очень полезная функция, и художники часто этим пользуются. Чтобы у вас не возникало проблемы, когда в отэкспорченной сцене появляются какие-то непонятные объекты, которых в 3DS MAX вроде бы нет, вам нужно знать, что 3DS MAX позволяет спрятать объекты тремя разными способами. 

1. Спрятать выделенные объекты с помощью контекстного меню:

Рисунок 5. Контекстное меню (вызывается правой кнопкой мыши).

Для того, чтобы показать все объекты сцены, спрятанные этим способом, нужно выбрать в том же меню “Unhide all”.

2. С помощью слоев:

Рисунок 6. Окно Layer Manager (вызывается Tools->Layer Manager).

3. С помощью панели Display:

Рисунок 7. Панель Display options.

Объект, спрятанный любым способом, не будет отображаться в окнах ViewPort и в списке объектов. Сам плагин не различает способа скрытия; в нем можно использовать единый метод INode->IsHidden().

Спрятанные треугольники

Рисунок 8. Панель face sub-object.

Кроме целых объектов, 3DS MAX позволяет скрывать отдельные треугольники. Плагин может запросить статус треугольника методом Face->Hidden().

Экспорт материалов

Во-первых, будьте готовы, что в один прекрасный день художник добавит в начало или конец названия материала пробел, и вы проведете два увлекательных часа в поисках ошибки 🙂 Поэтому пробелы в начале и конце названий материалов и объектов лучше сразу удалять автоматически.

Рисунок 9. Хотя этого не видно, на самом деле перед словом “citywall” добавлен пробел.

То же самое касается сходных по начертанию русских и английский букв (“a”, “p”, “e”, “o” и т.д.). Проще всего запретить использование русских букв в названиях, и тогда плагин сможет выдавать сообщения о подобных ошибках при экспорте. 

Multi/sub-object material

Поскольку в 3DS MAX используется архитектура “один объект – один материал”, то для применения разных текстур к выбранным треугольникам объекта используется специальный Multi/Sub-object material, который, в свою очередь, содержит ссылки на реальные материалы, применяемые к треугольникам. Multi/Sub-object material связывает с каждым из дочерних материалов уникальный id.

Рисунок 10. Multi/Sub-object материал.

Каждому треугольнику, соответственно, указывается id дочернего материала.

Рисунок 11. Панель Face sub-object.

Относительно id дочерних материалов нужно знать следующее:

  • в интерфейсе 3DS MAX нумерация id’ов начинается с единицы. В тоже время, в метод Mtl->GetSubMtl(i) предполагает нумерацию с нуля;
  • id’ы могут иметь пропуски (1,2,3,5,6,7) и начинаться не с 1. Будьте готовы, что метод Mtl->GetSubMtl() может вернуть NULL;
  • треугольнику может быть присвоен id несуществующего дочернего материала. В окне viewport треугольник будет отображаться черным цветом. Это ошибка художника, но вы все равно должны убедиться, что плагин в таком случае не “вылетает”;
  • треугольнику может быть присвоен id больше, чем максимальный id дочерних материалов. В отличие от предыдущей, такая ситуация в 3DS MAX считается нормальной. В этом случае нужно взять остаток от деления id на максимальный id.

В целом, обработка материалов выглядит так:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Mtl* mat = Node->GetMtl();
if (mat==NULL) return; // do not export objects without materials applied
DWORD numSubMat = mat->NumSubMtls();
Face* MFace = NodeMesh->faces;
for (DWORD f=0; f<NodeMesh->numFaces; f++, MFace++)
 {
   Mtl* faceMat = NULL;
   if (NumSubMtls==0)
       {
        faceMat = mat;
       }
        else
       {
        DWORD matID = MFace->getMatID();  //this is SubMatID-1
        matID = matID % NumSubMtls;
        faceMat = mat->GetSubMtl(matId);
       }
   //use faceMat (can be NULL)
   //...
 }

Но это еще не все. Как известно, художники – люди неаккуратные, и могут повторять одинаковые материалы в списке дочерних. Убедитесь, что это обрабатывается и не приводит к появлению лишних батчей (batch, drawcall).

Sub-region tiling

Рисунок 12. 3DS MAX позволяет использовать часть изображения в качестве текстуры.

3DS MAX при создании материала позволяет указывать, какую часть изображения нужно использовать в качестве текстуры.

Если без тайлинга эту проблему при экспорте можно решить модификацией UV координат, то при использовании тайлинга сделать ничего нельзя, т.к. ваш движёк вряд ли поддерживает такую возможность.

Лучше всего просто запретить использование этой фичи. Поскольку мы экспортим из пакета с большими возможностями в формат с ограниченными возможностями, то в общем случае, еще до начала моделирования, художник должен прочитать ваш документ, в котором кроме ограничений по количеству треугольников и текстур указаны и такие нюансы. 

Экспорт привязки вершин.

Иерархия из Dummy

Для привязки вершин к костям в 3DS MAX используются модификаторы Skin и Physique. Я опишу Physique, т.к. со Skin еще не разбирался.

Рисунок  13. Все вершины сферы привязаны к линку между двумя dummy. В 3DS MAX сфера будет двигаться при анимации и родительского, и дочернего dummy; в игре – только родительского. 

Во-первых, вы должны знать, что Physique двигает вершины вместе с линком, соединяющим дочерний и родительский объект. Поэтому анимировать вершины можно перемещением как родителя, так и наследника. В игровых движках же вершины реагируют исключительно на родительский объект.

До тех пор, пока аниматор использует Biped и Bones, все работает нормально, поскольку Bone реально представляет собой сцепку родитель-наследник, и перемещение наследника автоматически вызывает поворот родителя.

Но поскольку в 3DS MAX костью может быть любой объект (реально нужен только INode), художники часто применяют dummy. Вы сразу  должны довести до сведения, что двигать dummy, находящиеся в конце иерархии – нельзя. К счастью, Physique можно переключить в режим, соответствующий работе игры. Вот ответ Discreet support на эту тему: 

============= cut ============
Title Problems exporting Rigid data from Character Studio 3.X
Version 3.1 & 4.0
Keywords Rigid Links Translation
 

Question

I have character hooked up with dummy objects, which are being translated. Physique shows the deformation but the Character Studio SDK does not show these dummys effecting any vertices

Answer

This is a result of the new algorithm for Rigid blending in Physique 3.0 It allows child objects to have their translation as part of the deformation. In previous versions of Character Studio, a translated link would have no effect, only the parent object would have an influence. This causes confusion, as a common technique for artists was to turn on Rigid from the SKin Update, in the Physique rollout, so that they could see how the animation would run when exported. This however is no longer the case, as the SDK will not provide access to these node.

A solution to this is to use an INI switch provided in the latest version of Physique.

Edit the physique.ini file to add the following 

USE_2.2_RIGID_DEFORMATIONS=YES

This forces physique to use the old algorithm which will match SDK and game access to the deformer. THis will also mean that your exisiting tools will work as expected.

============= cut ============

Лучше всего, если ваш плагин будет включать эту опцию автоматически, и просить перезапустить 3DS MAX.

Линки-сплайны

Кроме этого обратите внимание, что по умолчанию Physique использует сплайновые линки. Для того, чтобы анимация  вершин совпадала с игрой, еще ДО ПРИВЯЗКИ ВЕРШИН нужно выделить все линки, и выключить параметр Continuity.

Рисунок 14. Панель настройки линков. 

Это важный момент – если это не было сделано, то придётся или мириться с несовпадением анимации, или полностью перепривязывать модель.

Типы вершин

Рисунок 15. Панель привязки вершин. 

В Physique используются три вида вершин: deformable(красные), rigid(зеленые) и root(синие). КРАСНЫЕ ВЕРШИНЫ ИСПОЛЬЗОВАТЬ НЕЛЬЗЯ. При блендинге, для красных вершин physique изменяет влияние линка на вершину в зависимости от удаленности этой вершины от линка. В играх, естественно, такие алгоритмы не применяются. Таким образом, чем дальше вершина находится от линка, тем меньше будет похожа анимация вершин в 3DS MAX на анимацию в игре. Доведите до сведения художников этот момент, иначе модель придется перепривязывать.

Также стоит сказать, что все настройки суставов (Tendons) в игре тоже работать не будут.

Ручное задание весов

Physique позволяет вручную задавать привязку каждой вершины к кости. Не знаю почему, но некоторые моделеры предпочитают неделями манаться с панелью привязки Physique (а она работает нетривиально), вместо того, чтобы один раз прочитать мануал и не мучаться. В результате возникают интересные ситуации, когда вершина привязана к одной и той же кости 3-4 раза, с разными коэффициентами. Честно говоря, я даже не знаю, как такого добиться, но это реально бывает.

Рисунок 16. Неправильная привязка.

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

Удалённые кости

Иногда бывает ситуация, когда в сцене удалена одна из костей, к которой привязаны вершины. Вообще-то, при этом вершины модели улетают в бесконечность, но этого может и не случаться, если у этой кости маленький вес. Поэтому нужно быть готовым к тому, что при запросе кости, к которой привязана вершина, PhysiqueInterface вернет NULL.

Иерархия

Link Constraint

Кроме использования стандартной иерархии, в 3DS MAX существует несколько альтернативных способов связывания объектов таким образом, чтобы они двигались как при использовании иерархии. Особенно популярным является Link Contstraint контроллер. В отличие от обычной линковки, с помощью него можно переназначать родителя во время анимирования. Например, сначала пистолет находится в кобуре (родитель – торс), а потом персонаж берет его в руки (родитель – ладонь).

Рисунок 17. Link Constraint контроллер.

В этом случае метод INode->GetParent() все равно возвращает родителя согласно обычной линковке (то есть Scene Root), в то время как игра требует определенной иерархии костей. Чтобы решить эту и подобные проблемы, нужно добавить возможность изменять иерархию при экспорте, например, указывая параметр “PARENTBONE=xxxx” в user-defined свойствах объекта.

Biped

Biped несколько “выбивается” из архитектуры 3DS MAX. На нем странно работают инструменты, он игнорирует масштабирование и иерархию. В связи с этим возможны ситуации, когда художник случайно отлинковывает части Biped’а, или перелинковывает их в другом порядке. Хотя на поведение Biped в 3DS MAX это абсолютно никак не влияет, в игру экспортится неправильная иерархия.

Рисунок 18. Руки Biped отлинкованы.

Можно, конечно, встроить в плагин специальный код для проверки правильности иерархии, но в данном случае достаточно просто знать, что такая ситуация возможна (предупрежден = защищен).

Экспорт нормалей

При создании плагина экспорта желательно максимально приблизить изображение, которое художник видит во вьюпорте 3DS MAX, к результату, который получается в игре.

3DS MAX предоставляет доступ к позициям вершин и треугольникам, но не к нормалям вершин. Поэтому нормали приходится считать самостоятельно. Алгоритм расчета нормалей приведен в теме “Computing face and vertex normals” в документации к 3DS MAX SDK. Нюансом здесь являются два момента. Во-первых, 3DS MAX использует взвешивание нормалей по углу треугольника, поэтому при расчете нормалей нужно использовать именно этот метод. 

Рисунок 19. Все треугольники принадлежат группе сглаживания 1, но некоторые ребра не сглажены из-за того, что прилегающие вершины не “склеены”.

Во-вторых, при сглаживании вершин 3DS MAX сравнивает не позиции вершин, а их id. Если выбрать вершину и выполнить операцию “break”, эта вершина будет разделена на несколько вершин – по одной на каждый смежный треугольник. В этом случае, граница сглажена не будет.

С другой стороны, если вы считаете карты нормалей, скажем, с помощью ATI Normal Mapper, то нормали и tanget space vector нужно считать именно так, так это делается там (это одна из причин, почему ATI Normal Mapper поставляется с исходниками). Если tangent space vectors будут посчитаны по-другому, то при применении полученных карт нормалей могут появляться видимые стыки в освещении.

Ссылки

1. “Заметки о разработке плагина для экспорта геометрии и ещё чуть-чуть”
http://www.gamedev.ru/community/toolcorner/articles/3d_export_stuff 

2. ATI Normal Mapper
http://www.ati.com/developer/tools.html 

3. Получение данных из 3ds Max
http://www.gamedev.ru/community/delphinarium/articles/getmax

4. Создание Export плагина для 3D Studio MAX
http://www.codenet.ru/progr/video/3D-Studio-Max-Export-Plugin.php