Версия для печати


Экспорт анимированных 3D персонажей из 3D STUDIO MAX 3.0 для DELPHI и OpenGL
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=363

Иван Дышленко
дата публикации 12-02-2001 00:00

Экспорт анимированных 3D персонажей из 3D STUDIO MAX 3.0 для DELPHI и OpenGL В данной статье излагается материал о проблемах связанных с созданием анимированных 3D пресонажей в приложениях использующих OpenGl. Статья расчитана в основном на продвинутого читателя. Для наилучшего восприятия желательно иметь опыт работы со средой программирования Delphi, а также весьма не помешает знать библиотеку OpenGL, графический пакет 3D Studio Max и его расширение Character Studio. Все это требуется потому, что данная статья не является справочником ни по одному из указанных инструментов и содержит описание только тех действий, которые необходимо выполнить для экспорта персонажей из 3D Studio Max и вывода их на экран средствами Delphi и OpenGL.

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

Содержание:

Введение

В свое время я здорово помучался, решая вопрос - каким же образом создатели игр ухитряются делать трехмерные персонажи двигающиеся в реальном времени. Я предположил, что части тела у персонажей отделены от основного тела, что позволяет независимо поворачивать и перемещать их. Знаете, в Direct3D даже есть понятие фрейма, фрейм - это основное тело, к нему прикрепляются другие тела. Когда фрейм движется, прикрепленные к нему объекты движутся вместе с ним, кроме того, прикрепленные объекты могут двигаться и самостоятельно не влияя на движение фрейма. Все это замечательно подходит для создания механических объектов и персонажей, но совершенно не годится для создания объектов живого мира. Для таких объектов характерна плавность линий и отсутствие изломов на местах стыков частей объекта. Создатели компьютерных игр замечательно решили эту проблему.

Как создается двумерная анимация? Рисуется несколько кадров движения, затем поледовательно выводятся на экран и таким образом создается иллюзия движения. То же самое происходит в современных трехмерных компьютерных играх. Создается несколько 3D моделей (сеток), характеризующих фазы движения персонажа в различные моменты времени, затем поледовательно выводятся на экран, создавая иллюзию движения. Возможно, это приводит к повышенному расходу оперативной памяти, поскольку все сетки желательно хранить в памяти, но зато значительно упрощается процесс программирования и, скорее всего, увеличивается скорость работы приложения.

Следующая проблема возникла при попытке экспорта объектов из 3D Studio Max в какой-либо открытый формат, например DXF. Нет ничего сложного в создании трехмерного персонажа с последующей его анимаций, если пользоваться 3D Studio и Character Studio, вся проблема состоит в том, как экспортировать объект чтобы потом файл с сетками объекта можно было использовать в своем приложении. Для этого требуется покадровый экспорт анимированного персонажа, то есть в итоге должен получится файл, содержащий несколько сеток объекта изображающих фазы движений объекта в различные моменты времени, или несколько файлов содержащих одну сетку соответствующую определенному кадру движения. Однако, несмотря на обилие поддерживаемых форматов файлов, 3D Studio Max не обладает возможностью покадрового экспорта трехмерных объектов. Так, напрмер, файл формата 3DS может хранить информацию о положении объекта, его повороте и масштабе, но не в состоянии сохранять деформации сетки в различных кадрах анимации, а именно это нам и нужно. Про файлы формата DXF и ASC даже говорить в данном случае смешно. Я объясню, почему нам нужно сохранять именно деформацию сетки. Дело в том, что наш объект должен состоять из единой, цельной сетки, а не из нескольких объектов, чтобы не было стыков на местах соединений конечностей с телом. Создать анимацию, так чтобы персонаж мог двигать своими конечностями, в этом случае, можно только деформируя сетку, а именно перемещая одни вершины сетки относительно других. Так, например, чтобы персонаж поднял руку нужно переместить вершины руки вверх относительно вершин тела. Теперь, я надеюсь, все понятно? Итак, оказалось, что 3DStudio не в состоянии сохранить подобную анимацию. Однако, не все так печально. Например, есть такой дополнительный модуль для 3DStudio, называется Bones Pro Max, а у него есть инструмент SnapShot, который позволяет делать снимки различных кадров движения объекта. В результате его работы у Вас на рабочем поле 3D Studio Max появляется целое стадо одинаковых трехмерных объектов в различных позах. Правду сказать, я его не нашел, да и выпущен он был уже давно еще под первую версию 3D Studio Max. Поэтому я решил идти другим путем и окунулся во внутренний язык 3D Studi Max - Max Script. Результатом моей деятельности стала простенькая утилита Meshes Export for Games and Animation (MEGA), которая позволяет делать все, о чем я сказал выше и некоторые другие полезные вещи.

Знакомство с утилитой MEGA V 1.0

Для ознакомления с этой утилитой Вам понадобится графический пакет 3D Studio Max 3.0 и, собственно, сама утилита. Она расположена в папке Utility и называется MEGA.ms. Это не исполняемый файл, а текстовый файл с набором команд для 3D Studio Max написанных на языке Max Script.

Сфера Запустите 3D Studio Max и создайте простой объект - сферу. Я полагаю, что даже те, кто никогда не видел этого графического редактора, без труда справятся с таким простым заданием.

Командная панель Контекстное меню Теперь, щелкайте на сфере правoй кнопкой мыши пока не появится контекстное меню. Как правило, с первого раза оно не появляется. В контекстном меню выберите строку Convert to Editable Mesh (Преобразовать в Редактируемую Сетку). Обратите внимание: объект, непременно, должен быть Редактируемой Сеткой, если в выходном файле мы хотим получить список вершин и граней, иначе мы получим имя объекта и его свойства, такие как, радиус, количество сегментов - для сферы, высоту, ширину и глубину - для параллелепипеда и т.д.

Диалог экспорта Перейдите на командную панель (она расположена справа) и выберите вкладку с изображением молотка. Это вкладка утилит. Нажмите кнопку MAXScript, внизу панели развернется свиток MAXScript'a. Нажмите кнопку Запуск Макроса, появится диалоговое окно открытия файла. Запустите файл MEGA.ms. Внизу командной панели в списке утилит должна появится надпись MEGA, однако это еще не означает, что утилита уже запущена. Чтобы ее запустить, необходимо раскрыть спиок утилит и выделить строку MEGA. Внизу панели должен раскрыться свиток MEGA.

Введите в поле From зачение 1, в поле To - 100, в поле Step - 100. Нажмите кнопку Save As..., в диалоговом окне введите имя файла, куда бдете сохранять и нажмите кнопку сохранить. Объект экспортирован в файл с расширением GMS.

Как работает утилита: При экспорте файла, берется значение из поля From и ползунок счетчика кадров расположенный внизу экрана премещается на позицию, соответствующую этому значению. Затем в выходной файл экспортируется объект в том виде, в каком он пребывает на данный момент на экране. После чего снова передвигается ползунок кадров на величину, введенную в поле Step. Снова записывается модель соответствующая этому кадру. И так до тех пор, пока ползунок не переместится на позицию соответствующую значению, введенному в поле To. Поскольку в данном примере мы не создавали анимацию, то нам нужен был только один кадр. Утилита экспортировала кадр №1, затем добавила к нему значение 100. Номер кадра стал равен 101. Поскольку это значение больше значения введенного в поле To, процесс экспорта на этом остановился. Если бы в поле From было введено значение 0, то было бы экспортировано 2 кадра с номерами 0 и 100 соответственно. Если пометить галочкой опцию Selected Only, то экспортироваться будут только выделенные объекты, это иногда бывает очень нужно, в противном случае будут экспортированы все объекты сцены. Теперь рекомендую рассмотреть формат файла GMS.

Формат файла GMS

Файл GMS это текстовый файл открытого формата, что означает, что даже человек не знакомый с его описанием может создать приложение, считывающее из него информацию. Тем не менее, приведу на всякий случай описание этого файла.

New object // Указывает на начало нового объекта, следующая строка указывает тип объекта

TriMesh() // Объект - сетка

     numverts numfaces // Указывает, что следующая строка содержит количество вершин 
       и граней для данного объекта

Mesh vertices:

     // Здесь располагается блок вершин объекта в виде координат X Y Z

end vertices

Mesh faces:

     // Здесь располагается блок граней объекта в виде индексов 1 2 3, 
    где каждый индекс - индекс в массиве вершин, указывает на вершину грани

end faces

Faset normals:

     // Здесь располагается блок фасетных нормалей в виде координат X Y Z. 
    Их количество равно количеству граней

end faset normals

Smooth normals:

     // Здесь располагается блок сглаживающих нормалей в виде координат X Y Z. 
    Их количество равно количеству вершин.

end smooth normals

end mesh // Конец описания объекта Tri Mesh

end of file // Конец файла

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

New object // Указывает на начало нового объекта, следующая строка указывает тип объекта

<Тип объекта>, например: Box

     // Здесь идут параметры, зависящие от типа объекта (Поверхности Безье и NURBS - 
      поверхности не поддерживаются)

end <Тип объекта> // Конец описания объекта

end of file // Конец файла

Загрузка файла формата GMS в Delphi

Пример загрузки файла GMS находится в папке Ch01. В проекте присутствует два модуля: frmMain.pas и Mesh.pas. Откомпилировав и запустив проект на выполнение вы должны увидеть вращающийся Тор (по-нашему: "Баранка"). Несмотря на то, что объект можно считать стандартным, он был в 3D Studio преобразован в сетку, поэтому в данном случае это именно сетчатый объект. Нажав пункт меню "загрузить", вы можете посмотреть любой объект из папки GMS или загрузить свою сферу, которую сделали сами, если правильно руководствовались моими инструкциями в разделе: Знакомство с утилитой MEGA V1.0. Теперь рассмотрим данный пример подробно. Почти весь код модуля frmMain.pas написан не мной. Он взят из книги "OpenGL графика в проектах Delphi" Михаила Краснова. Этот модуль выполняет инициализацию приложения и циклическую функцию отрисовки окна, поэтому подробно мы его рассматривать не будем. Если код покажется Вам непонятным, значит Вы недостаточно знакомы с OpenGL, в этом случае Вам надлежит обратится к первоисточнику (в смысле - к книге). Код модуля Mesh.pas выполняет загрузку данных из файла и отображение объектов в окне. Рассмотрим его подробнее:

Type // Объявление типов данных

     PGLVertex = ^TGLVertex; // Указатель на вершину

     TGLVertex = record

     x,y,z : GLFloat; // Вершина, как три значения с плавающей точкой

     end;

      

     PGLVector = ^TGLVector; // Указатель на вектор

     TGLVector = array[0..2] of GLFloat; // Вектор, как массив из трех элементов с плавающей точкой

      

     PGLFace = ^TGLFace; // Указатель на грань

     TGLFace = array[0..2] of GLInt; // Грань, как массив из трех целочисленных значений

      

     PGLVertexArray = ^TGLVertexArray; // Указатель на массив вершин

     TGLVertexArray = array[Word] of TGLVertex; // Массив вершин

      

     PGLFacesArray = ^TGLFacesArray; // Указатель на массив граней

     TGLFacesArray = array[word] of TGLFace; // Массив граней

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

Теперь рассмотрим описание объекта сетка:

TGLMesh = class

     Vertices : PGLVertexArray; // Массив вершин объекта - сетка

     Faces : PGLFacesArray; // Массив граней

     FasetNormals : PGLVertexArray; // Массив фасетных нормалей

     VertexCount : Integer; // Количество вершин

     FacesCount : Integer; // Количество граней

     fExtent : GLFloat; // Коэффициент масштабирования

     Extent : GLBoolean; // Флаг масштабирования

     public

     procedure LoadFromFile( const FileName : String ); // Загрузка

     procedure CalcNormals; // Расчет нормалей

     procedure Draw; // Отрисовка

     destructor Destroy; override; // Уничтожение с очисткой массивов

     end;

Здесь пояснений практически не требуется. Можно лишь отметить, что Extent служит для того, чтобы объект загнать в размеры в пределах (-1, 1), я сделал это для того, чтобы объект любого размера не мог вылезти за пределы окна. Вообще говоря, в 3D Studio Max не сложно масштабировать объект так, чтобы координаты вершин попали в интервал (-1, 1), но на этапе создания модели думать об этом совсем не хочется.

procedure TGLMesh.LoadFromFile; // Загрузка файла
var
     f : TextFile;
     S : String;
     i : Integer;
     Vertex : TGLVertex;
     Face : TGLFace;
     MaxVertex : GLFloat;
begin

     AssignFile(f,FileName);
     Reset(f);
     // Пропускаем строки, пока не попадется 'numverts numfaces'
	repeat
		ReadLn(f, S);
	until (S = 'numverts numfaces') or eof(f);
     // Читаем количество вершин и граней

     Readln(f,VertexCount,FacesCount);
     // Выделяем память для хранения сетки

     GetMem(Vertices,VertexCount*SizeOf(TGLVertex));
     GetMem(Faces,FacesCount*SizeOf(TGLFace));
     GetMem(FasetNormals,FacesCount*SizeOf(TGLVector));
    
     ReadLn(f, S); // Пропускаем строку "Mesh vertices"

     // Считываем вершины
     for i := 0 to VertexCount - 1 do begin

        Readln(f,Vertex.x,Vertex.y,Vertex.z);
        Vertices[i] := Vertex;
     end;

     ReadLn(f, S); // Пропускаем строку "end vertices"
     ReadLn(f, S); // Пропускаем строку "Mesh faces"

     // Считываем грани
     for i := 0 to FacesCount - 1 do begin
       Readln(f,Face[0],Face[1],Face[2]);
       Face[0] := Face[0] - 1;
       Face[1] := Face[1] - 1;
       Face[2] := Face[2] - 1;
       Faces[i] := Face;
     end;
      
     CloseFile(f);

     // Рассчитываем масштаб

     MaxVertex := 0;

     for i := 0 to VertexCount - 1 do begin
       MaxVertex := Max(MaxVertex,Vertices[i].x);
       MaxVertex := Max(MaxVertex,Vertices[i].y);
       MaxVertex := Max(MaxVertex,Vertices[i].z);
     end;

     fExtent := 1/MaxVertex;
      
     CalcNormals;

end;

Здесь могут быть непонятны следующие моменты: В блоке считывания граней я вычитаю единицу из каждого индекса вершины, считанного из файла. Делается это потому, что в программе индексы нумеруются, начиная с нуля, а в файле GMS - начиная с единицы. Процедура CalcNormals служит для расчета нормалей и взята из книги "OpenGL графика в проектах Delphi" Михаила Краснова. О том, что такое нормали и зачем они нужны я расскажу в разделах "Фасетные нормали" и "Сглаживающие нормали".

procedure TGLMesh.Draw;
var
     i : Integer;
     Face : TGLFace;

begin

     if Extent then glScalef(fExtent,fExtent,fExtent);

     for i := 0 to FacesCount - 1 do begin

       glBegin(GL_TRIANGLES);
       Face := Faces[i];
       glNormal3fv(@FasetNormals[i]);
       glVertex3fv(@Vertices[Face[0]]);
       glVertex3fv(@Vertices[Face[1]]);
       glVertex3fv(@Vertices[Face[2]]);

       glEnd;

     end;

end;

Здесь все понятно. Сначала, если установлен флаг масштабирования, устанавливается масштаб одинаковый по всем осям, затем в цикле рисуются треугольники. Перед началом рисования треугольника объявляется нормаль к нему. В качестве параметров передаются не конкретные значения, а указатели на них.

destructor TGLMesh.Destroy;
begin

     FreeMem(Vertices,VertexCount*SizeOf(TGLVertex));
     FreeMem(Faces,FacesCount*SizeOf(TGLFace));
     FreeMem(FasetNormals,FacesCount*SizeOf(TGLVector));

end;

Здесь тоже все понятно, просто освобождается память, занятая объектом. Вызовы процедур загрузки и отрисовки объекта находятся в модуле frmMain и не представляют ничего интересного.

... >> Продолжение статьи

В начало



К материалу прилагаются файлы: