Войти
Уголок tool-программСтатьи

Заметки о разработке плагина для экспорта геометрии и ещё чуть-чуть

Автор:

версия документа: черновик2

Введение
Начало
Наш формат - самый лучший
Этап первый
Проблема 1. Чеширские кубики
Проблема 2. Комплекс лилипута
Проблема 3. Куча мала
Этап 2. Нормальные нормали
Проблема 4. Нормаль дело тонкое.
Проблема 5. В поисках ребра
Этап третий

Введение


Созданию подобных плагинов посвящено довольно много статей, однако в них часто пропускаются те тривиальные вещи, на которых программист, ответственный за разработку вспомогательных утилит часто спотыкается при первой попытке свалять утилиту нахрапом за два часа. В результате тратит много времени, своего и моделлеров, которое можно было бы с пользой потратить на отдых от тяжёлых трудовых будней.
В данной статье я постараюсь рассмотреть несколько подобных тривиальных, но порой неочевидных вопросов, что поможет читателю в дальнейшем (ну я надеюсь, что поможет, а не усугубит из-за стиля подачи материала).
Статья может быть интересна программистам вспомогательных утилит. Стиль написания - разговорный, по возможности без ущерба к точности изложения. В значительной степени изложение абстрактно и не привязано к конкретному SDK.

Начало


Начинается работа с первого пришествия моделлера. А точнее с пришествия недовольного моделлера, который привык работать и получает значительные результаты именно в программе А версии Б, а не в том собственноручно разработанном 3d редакторе моделей, которым вы лично гордитесь и всегда пользовались до этого.
Моделлер формулирует следующие требования:
1.Я буду работать в А, ибо в нём я могу делать то что нужно, в какие нужно сроки и получать удовольствие от этого.
2.Я хочу видеть результат того же что и в А во вьювере моделей проекта.
Естественно, он не скажет вам, что ему нужны группы сглаживания, материалы, корректный экспорт текстурных координат для вершин, которых в его любимом редакторе их задано несколько, и прочие гадкие мелочи. Ему просто не придёт в голову - что кое-чего может и не быть по умолчанию. А вам не придёт в голову спросить его, потому что вы об этом ничего могли и не слышать никогда (вообще говоря, программист вспомогательных утилит должен знать всего по чуть-чуть - но чуть-чуть может быть мало).
Через пару-тройку часов будет получен экспортёр версии 0.1 :) build 5, который будет с негодованием отброшен разъярённым моделлером, его вера в вас пошатнётся и настроение в дружном коллективе начнёт коллективно падать.
Описанного ужаса, конечно же, можно избежать, если прочитать эту на первый взгляд совершенно несерьёзную статью, которая должна стать детальным пособием "Как не нужно писать статьи".

Наш формат - самый лучший


Это, конечно же, заблуждение - но заблуждение, греющее душу. Чаще всего наш формат сильно упрощён и в процессе развития проекта (особенно первого проекта) обрастает всё большими и большими заплатами, прибитыми кое-как ржавыми гвоздями к основному коду. Поэтому важно хорошенько продумать формат файла модели, прежде чем приступать к работе вообще. Впрочем, можно со мной и не соглашаться - и свято верить в свой формат.
Естественно, в каждом конкретном случае от формата файла могут требоваться какие-то специфичные для игры свойства. Однако в большинстве случаев модель должна сохранять: данные вершин (координаты, нормаль, текстурные координаты), индексы вершин для каждого полигона, данные о текстурах и материалах, данные для скелетной анимации. Причём, сами текстуры, материалы и анимацию костей (ключевые кадры) в процессе разработки удобнее хранить отдельно от основного файла моделей. Что упрощает модификацию каждой отдельной составляющей без затрагивания остальных. Более того, каждая составляющая может  редактироваться в различных утилитах, что опять же очень удобно.
В процессе рассмотрения кода экспорта геометрии будем использовать следующий весьма абстрактный класс:
class sf_group {
    bool    placeVerts(sf_vertex *vertexes, int count, int from=-1);
    bool    placeFaces(sf_face *faces, int count, int from=-1);
    int    countVerts();
    int    countFaces();
    const sf_face  *getFace(int index);
    const sf_vertex  *getVertex(int index);

    bool    Save();
};

// хранит текстурные координаты
struct sf_uv {
    float u;
    float v;
};

// содержит основные данные для вершины
struct sf_vertex {
    sf_vector3 pos;    // позиция
    sf_vector3 normal; // нормаль

    union {            // текстурные координаты
       float tc[2];
       sf_uv tex;  
    };
};

// вот так выглядит треугольник в нашем супер формате (sf_)
struct sf_face {
    unsigned short int id[3];
};

placeVerts()
  помещает в группу count вершин последовательно из массива вершин начиная с указанного индекса (from). Если указано -1, то помещать начиная со следующего за последним индекса.

placeFaces()
  работает аналогично, за единственным отличием - в группу помещаются треугольники.

countVerts() и countFaces()
  возвращают число вершин и треугольников уже помещённых в группу.

getFace() и getVertex()
  возвращают соответственно треугольник и вершину с указным индексом.

sf_group, sf_face, sf_vertes - классы представляющие внутренний формат группы треугольников, треугольника (фэйса, фасета) и вершины соответственно. Все группы объединяются в сущность sf_file.

Ну а последняя функция осуществляет сброс всей информации в какой-либо native формат данных нашего проекта. Я совсем забыл предупредить, что коду сохранения и загрузки в данной статье будет уделено очень мало внимания. А точнее - совсем нисколько.
 

Этап первый


Итак, моделлер нас довёл до такого состояния, что мы с вами осознали необходимость плагина или внешней утилиты и неизбежное выключение любимой офисной игры Q? и запуск ближайшей интегрированной среды разработчика. Скрепя сердца и тихо - но незлобно - поругиваясь, тянетесь к коробочке с А SDK, любезно предоставленной кем-то свыше, возможно даже компанией A Software по льготным ценам.

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

Первое, что мы обязаны вынести из документации - знание о том, как представлена сцена (обычно иерархически, обход дерева реализуется с помощью механизма callback-функций, которые необходимо определить, чтобы колесо экспорта завертелось). Например, в MAX SDK, фактически при каждом вызове callback-функции будет предлагаться тот узел (узел иерархии) сцены, который неплохо было бы экспортировать, а задача экспортёра обрабатывать посылаемые ему данные.

Второе это основные классы представляющих математические типы данных, систему координат и систему измерений.

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

Итак, с помощью объекта АMesh (класс, представляющий объект в виде набора треугольников) мы получили все вершины и все треугольники. Загнали в наши группы и сохранили. Казалось бы ничего сложного пока не экспортировали.

Просто сохранили все числа не применяя никаких дополнительных операций над ними.

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

Проблема 1. Чеширские кубики


Моделлер поместил в один меш два кубика, один слева от плоскости Oxz, другой справа.

Один кубик смотрится великолепно. Величие и строгость его дизайна заставляет думать о славном будущем нашей компании. Но вот второй кубик: если включено отсечение невидимых граней (culling) его не видно совсем или видно какие-то огрызки. Очевидно, где-то что-то не так.

Суть проблемы состоит в том, что моделлер решил не мучить себя созданием абсолютно идентичного великолепного кубика, а взял и скопировал его, а потом зеркально отразил. Таким образом он всегда поступает, когда создаёт симметричные объекты (например 4 колеса одной машины). При этом c нашей точки зрения меняется порядок обхода вершин треугольников, составляющих группу.  В принципе, если редактор разработан исключительно для создания моделей, которые будут выводиться с помощью обычного видео ускорителя, то при операции отражения он сможет автоматически поменять порядок вершин или хотя бы спросить пользователя о необходимости данной процедуры. Однако редакторы, ориентированные на рэй-трейсинг (и не только) могут предательски даже не заикнуться об этом.

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

bool isRightFrame(AMatrix43 mat) {
    return (a_dotProd(a_crossProd(mat.row(0), mat.row(1)),mat.row(2))<0.0) ? true : false;
};

// узел иерархии сцены, содержит матрицу трансформации узла и пару полезных функций
class ANode  {
    // истинно если узел сцены сводим к треугольникам.
    bool  isMesh();
    // создаёт объект AMesh, для данного узла
    AMesh  convertToMesh();
    // возвращает матрицу трансформации
    AMatrix43  getTransformMatrix();
};

// класс AMatrix представляет данные матрицы вида 4x3 и средства доступа к этим данным.
class AMatrix43 {
   // возвращает указанную строку матрицы
   APoint3 row(int n);
   // убрать компоненту масштабирования из матрицы
   void maskScale();
   // убрать компоненту переноса из матрицы
   void maskTrans();
};

  // представляет вектор или точку 3х мерного пространства
class APoint3 {
    float x, y, z;
};

// класс AFace предоставляет доступ к данным треугольника
class AFace {
public:
    int          v[3];      // индексы вершин
    unsigned int sm_group;  // параметр - принадлежность группам сглаживания
};

// класс AVert содержит даные вершины 
class AVert { 
public:
    float x, y, z;  // координаты
};

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

18 января 2006 (Обновление: 6 фев. 2006)

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