Программирование графики с использованием Direct3D

         

Создание MFC приложения


Создание приложений с использованием мастера AppWizard настолько легко, что мы можем создать один проект для практики прямо сейчас. Запустите программу Visual C++ Developer Studio и выберите пункт New в меню File. Будет выведено диалоговое окно New, представленное на рис. 1.3. Выберите пункт Project Workspace и щелкните по кнопке OK.


Рис. 1.3. Окно New программы Visual C++ Developer Studio

После этого будет открыто диалоговое окно New Project Workspace (рис. 1.4). Это окно предлагает вам выбрать один из мастеров AppWizard, установленных на вашем компьютере. Мы будем использовать MFC AppWizard для создания многодокументного приложения, использующего библиотеку MFC. Выберем пункт MFC AppWizard(exe). Теперь введем имя проекта. На рис. 1.4 видно, что для проекта мы выбрали имя Sample.


Рис. 1.4. Окно New Project Workspace

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

Щелкните по кнопке Create и вам будет показано первое диалоговое окно мастера MFC AppWizard. Это окно вы можете увидеть на рис. 1.5. Здесь вам предлагается выбрать тип вашего приложения — однодокументное приложение (SDI), многодокументное приложение (MDI) или диалоговое приложение. В нашем примере мы будем создавать многодокументное приложение (MDI), которое мастер предлагает по умолчанию.


Рис. 1.5. Первое диалоговое окно мастера MFC AppWizard

Щелкните по кнопке Next, и вы перейдете к следующему диалоговому окну. В диалоговых окнах со второго по пятое задается множество параметров вашего нового проекта. Сейчас мы ничего не будем менять в этих окнах и воспользуемся установленными по умолчанию значениями. На рис. 1.6. показано шестое и последнее диалоговое окно мастера MFC AppWizard.


Рис. 1.6. Последнее диалоговое окно мастера MFC AppWizard

Последнее диалоговое окно позволяет вам изменить имена классов, которые будут использоваться в новом проекте. Мастер AppWizard создает имена классов добавляя в конец имени проекта идентификаторы типа App, View или Doc. Для наших целей эти имена вполне подходят. Теперь можно щелкнуть по кнопке Finish.

Перед созданием приложения мастер AppWizard покажет окно New Project Information (рис. 1.7) в котором будут отображены выбранные параметры.


Рис. 1.7. Окно New Project Information

Это ваш последний шанс посмотреть, что вы выбрали. Щелчок по кнопке OK создаст приложение. Вы можете скомпилировать новый проект, нажав клавишу F7 и запустить приложение, нажав клавишу F5.



Создание объектов Direct3D


Главная цель интерфейса Direct3DRM — создание других объектов Direct3D. Большинство функций интерфейса Direct3DRM имеют префикс «Create» (создание). Функции, создающие объекты, перечислены ниже:

CreateAnimation()

CreateAnimationSet()

CreateDeviceFromClipper()



CreateDeviceFromD3D()

CreateDeviceFromSurface()

CreateFace()

CreateFrame()

CreateLight()

CreateLightRGB()

CreateMaterial()

CreateMesh()

CreateMeshBuilder()

CreateObject()

CreateUserVisual()

CreateShadow()

CreateTexture()

CreateTextureFromSurface()

LoadTexture()

CreateViewport()

CreateWrap()

Обычно нет необходимости использовать все эти функции. Некоторые функции практически идентичны. Например, единственное различие между функциями CreateLight() и CreateLightRGB() состоит в том, каким образом задан цвет освещения.

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



Создание порта просмотра


Третий и последний этап, реализуемый функцией CreateScene() — создание порта просмотра. Выполняющий эти действия код выглядит следующим образом:

d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport);

Перед созданием порта просмотра необходимо создать фрейм, который будет использоваться для задания местоположения и ориентации порта просмотра. Вызов функции CreateFrame() интерфейса Direct3DRM инициализирует указатель camera. Вспомните, что указатель camera предоставляется классом RMWin поэтому в функции CreateScene() он не объявляется.

Местоположение фрейма camera задается посредством функции SetPosition(). Местоположение и ориентация фрейма камеры определяют точку с которой будет просматриваться сцена. Вспомните, что при создании сетки и ее привязке к фрейму мы не изменяли местоположение фрейма. Это значит, что сетка отображается в начале координат <0, 0, 0>. Если мы хотим увидеть эту сетку в созданной области просмотра необходимо сместить фрейм камеры от начала координат. Мы используем функцию SetPosition() чтобы разместить фрейм камеры на 50 единиц дальше начала координат.

Область просмотра создается функцией CreateViewport(). Первый аргумент — device был создан функцией CreateDevice(). Второй аргумент — это только что созданный фрейм camera. Следующие четыре аргумента определяют местоположение и размеры области просмотра. Местоположение области просмотра — 0, 0, (верхний левый угол устройства) а размеры совпадают с размерами устройства (вспомните, что при создании устройства мы использовали размеры клиентской области окна). Последний аргумент представляет собой адрес указателя на новую область просмотра, который будет инициализирован по завершении функции.

В конце функция CreateScene() возвращает TRUE что указывает классу RMWin на успешное создание сцены.



Создание сетки


Первый этап требует создания указателя на интерфейс Direct3DRMMeshBuilder. Функция Load() интерфейса Direct3DRMMeshBuilder применяется для загрузки сетки из файла. Если вызов функции Load() завершается неудачно (файл отсутствует или имеет неправильный формат), выводится окно сообщения и функция возвращает FALSE, сигнализируя о необходимости прекратить выполнение приложения. Давайте взглянем на эту часть кода:

d3drm->CreateMeshBuilder(&meshbuilder); r = meshbuilder->Load(meshname, NULL, D3DRMLOAD_FROMFILE, NULL, NULL ); if (r != D3DRM_OK) { CString msg; msg.Format("Failed to load file '%s'\n", meshname); AfxMessageBox(msg); return FALSE; } ScaleMesh(meshbuilder, D3DVALUE(25));

Если сетка успешно загружена, она масштабируется функцией ScaleMesh(). ScaleMesh() — это удобная функция, предоставляемая классом RMWin. Мы используем здесь эту функцию для масштабирования сетки, чтобы гарантировать корректное отображение сетки, независимо от ее оригинальных размеров. Функция ScaleMesh() будет описана позже в этой главе.

Затем конструктор сеток добавляется к фрейму. Эта часть кода выглядит следующим образом:

LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(0.1)); meshframe->Release(); meshframe = 0;

Функция CreateFrame() интерфейса Direct3DRM применяется для инициализации указателя meshframe. Обратите внимание, что в качестве первого аргумента функции CreateFrame() передается указатель на фрейм scene. Это означает, что новый фрейм (meshframe) будет дочерним для фрейма scene.

Затем вызывается функция AddVisual() для присоединения конструктора сеток meshbuilder к новому фрейму. К одному фрейму можно добавить несколько сеток, но обычно к одному фрейму присоединяется только одна сетка.

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

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



Создание сеток посредством конструктора сеток


Для создания экземпляра интерфейса Direct3DRMMesh может применяться конструктор сеток. Обычно конструктор сеток используется для загрузки сетки с диска, назначения цветов и текстур, настройки граней, вершин и нормалей. Затем функция CreateMesh() интерфейса Direct3DRMMeshBuilder используется для создания объекта Direct3DRMMesh. Эта техника удобна, но в результате все грани сетки будут принадлежать одной группе. Для создания сетки с несколькими группами необходимо применять функцию AddGroup().



Создание собственного дерева зависимостей


Показанное на рис. 4.2 дерево зависимостей может быть разделено на две части. Узлы вблизи корня представляют интерфейсы, присутствующие во всех приложениях Direct3D, а остальные узлы представляют интерфейсы меняющиеся от приложения к приложению. Мы назовем первую группу стандартными интерфейсами (standard interfaces), а вторую — специализированными интерфейсами приложения (application specific interfaces). На рис. 4.3 отображено данное разделение дерева.


Рис. 4.3. Отделение стандартных интерфейсов от специализированных интерфейсов приложения

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



Создание текстур


Простейший способ создания текстуры— использование функции LoadTexture() интерфейса Direct3DRM:

LPDIRECT3DRM texture; d3drm->LoadTexture("texture.bmp", &texture);

Функция LoadTexture() получает в качестве аргумента имя файла BMP или PPM и использует растровое изображение из файла для создания текстуры.

Также текстуры могут быть загружены из ресурсов программы с помощью функции LoadTextureFromResource(), как показано в следующем примере:

LPDIRECT3DRMTEXTURE texture; HRSRC id = FindResource(NULL, MAKEINTRESOURCE(IDR_SAMPLETEXTURE), "TEXTURE"); d3drm->LoadTextureFromResource(id, &texture);

Функция LoadTextureFromResource() интерфейса Direct3DRM получает в качестве параметра идентификатор ресурса и создает текстуру из ресурса, определяемого значением переменной id.

Для внутреннего представления текстур в Direct3D применяются поверхности DirectDraw. Функция LoadTextureFromSurface() позволяет использовать в качестве текстуры существующую поверхность DirectDraw.



Способы наложения текстуры


Способ наложения текстуры (texture wrapping) определяет, каким образом текстура будет соединяться с объектом. Простейший способ наложения предполагает, что текстура попадает на объект как выстреленная из пушки. В этом случае цвета текстуры проходят объект насквозь и появляются с другой его стороны. Такой метод обычно называется плоским наложением (небольшой парадокс, поскольку вы не можете обернуть объект, чем-то, что действительно является плоским). Рис.2.19 показывает результат плоского наложения текстуры, изображенной на рис. 2.18 на куб.


Рис. 2.19. Плоское наложение текстуры

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

Другой метод наложения называется цилиндрическим. При цилиндрическом наложении текстура сворачивается в цилиндр, внутри которого находится объект. Этот метод показан на рис. 2.20. Обратите внимание на шов в месте встречи краев текстуры.


Рис. 2.20. Цилиндрическое наложение текстуры

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

Более детальное обсуждение методов наложения текстур содержится в главе 5.



Структура каталогов


Структура каталогов на CD-ROM показана на рис. A.1.


Рис. A.1. Структура каталогов на CD-ROM

Исходные коды всех рассматриваемых в книге демонстрационных программ находятся в каталоге SRC. Исполняемые файлы приложений помещены в каталог BIN. Программа установки копирует содержимое этих двух каталогов на жесткий диск вашего компьютера.

Каталог 3DS содержит файлы 3D Studio, которые были использованы при создании сеток для демонстрационных программ. Эти файлы не требуются для использования исходного кода программ из книги, но могут оказаться полезными, если вы используете 3D Studio или утилиты, которые способны работать с файлами формата 3DS.

Каталог MESHES содержит файлы X и MRF. X-файлы могут содержать сетки, анимации, иерархии фреймов и целые сцены. Однако, большинство файлов в каталоге MESHES содержат только сетки. Файлы MRF это X-файлы, содержащие сетки, образующие последовательность трансформаций. Структура и способы создания файлов MRF подробно обсуждались в главе 8. Каталог TEXTURES содержит файлы с расширениями BMP и PPM, которые можно использовать в качестве текстур.

Файлы из каталогов MESHES и TEXTURES не используются демонстрационными программами, поэтому нет никакой необходимости копировать их на жесткий диск. В каталоге с исходным кодом каждого из приложений есть копии всех необходимых ему сеток и текстур.

Каталог EXTRAS содержит две вспомогательных программы: Texture Magic и 3DS2POV. Мы поговорим об этих утилитах чуть позже.



Структура классов


В отличие от большинства демонстрационных программ на CD-ROM, функциональность приложения MorphPlay разделена между двумя классами. Функции, необходимые для выполнения трансформации сосредоточены в классе MorphWin, а вся специфическая для данного приложения функциональность находится в классе MorphPlayWin. Иерархия классов приложения показана на рис.8.5.


Рис. 8.5. Иерархия классов приложения MorphPlay

Изоляция функций трансформации в классе MorphWin позволяет упростить создание ваших собственных приложений, использующих трансформацию. Наследование класса окна от класса MorphWin (а не от класса RMWin) означает, что класс окна будет наследовать возможности трансформации.



Студия Разработчика


Visual C++ — это набор инструментальных средств для создания приложений, работающих в Windows. Все эти инструменты объединены в Студию Разработчика (Developer Studio). Вы можете использовать Студию Разработчика, чтобы создавать минимальные приложения, добавлять функциональные возможности к существующим программам и создавать или редактировать ресурсы, такие как меню, диалоговые окна и значки. В Студию Разработчика также входят компилятор, отладчик и редактор. На рис. 1.1 показано главное окно программы Visual C++ Developer Studio.


Рис. 1.1. Главное окно программы Visual C++ Developer Studio



Техника удаления невидимых поверхностей


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



Текстуры граней


Для присоединения текстур к граням используются следующие функции интерфейса Direct3DRMFace:

GetTexture()

GetTextureCoordinateIndex()

GetTextureCoordinates()

GetTextureTopology()

SetTexture()

SetTextureCoordinates()

SetTextureTopology()

Более подробно мы поговорим о текстурах во время обсуждения интерфейса Direct3DRMTexture.



Тени


Данная глава называется «Источники света и тени», и мы уже обсудили пять типов источников света, поддерживаемых Direct3D, теперь настало время поговорить о тенях.

Однако перед тем, как начать, следует обратить внимание, что в Direct3D источники света и тени почти не влияют друг на друга. В Direct3D тень— это объект, который добавляется к сцене чтобы имитировать поведение реальной тени.



Texture Magic


Прогулки по Интернету часто переоценивают. Однако, случается, что они оправдывают себя. Так случилось и с Texture Magic. Texture Magic— это предназначенная для создания текстур утилита, которую я нашел в Сети и которая произвела на меня весьма благоприятное впечатление. Ее написал Скотт Палтз (Scott Pultz). Для своей утилиты Скотт создал Web-страницу, расположенную по адресу www.eskimo.com/~scott/povtext.html. Чтобы воспользоваться программой Texture Magic вам понадобится утилита для трассировки лучей POV-ray. Вы можете получить POV-ray в сети CompuServe (GO POVRAY), или по адресу ftp.povray.org.

Texture Magic написана умно, поскольку хотя ее специализацией является создание текстур, она может использоваться в качестве внешнего интерфейса к POV-ray. Это позволяет назначать текстуры и модифицировать исходный код POV-ray с помощью удобного и грамотно организованного интерфейса. Внешний вид окна Texture Magic показан на рис. A.3.


Рис. A.3. Texture Magic



Типы данных Direct3D


Direct3D определяет несколько типов данных, которые несомненно будут снова и снова применяться в ваших программах. В данном разделе мы обсудим эти типы данных.



Типы источников света


Источники света могут быть нескольких типов. Типичные источники света это рассеянный свет, точечное освещение и зональное освещение.



Точечное освещение


Точечный источник света (point light) излучает свет во всех направлениях. Точечный источник света имеет заданное местоположение, но не имеет ориентации. Точечное освещение требует значительного времени на вычисления, поскольку свет от источника распространяется во всех направлениях. Однако часто расход времени на вычисления бывает оправданным из-за реалистичных эффектов, которые создает точечное освещение. Иногда точечное освещение называют всенаправленным светом (omni или omni-directional light).



Точечный источник света


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



Точечный свет


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

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



Трансформация


Термин «трансформация» часто ассоциируется с телевизионной рекламой или спецэффектами в фильмах, когда один объект плавно превращается в другой. Обычно начальная и конечная форма объекта весьма отличаются. В данном разделе мы обсудим общие принципы трансформации.

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

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

Сетка, являющаяся частью набора трансформируемых сеток, называется шагом трансформации (morph target). В нашем обсуждении мы не будем касаться вопросов разработки и создания шагов трансформации. Мы сосредоточимся на том, как выполнить трансформацию, включающую два или более совместимых шага.

Формально трансформация означает вычисление позиций вершин сетки, основываясь на следующих трех критериях:

Позиция вершины на начальном шаге трансформации.

Позиция вершины на конечном шаге трансформации.

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

Предположим, что сетка начального шага трансформации представляет собой птицу, сетка конечного шага трансформации— самолет, а число промежуточных изменений равно 100. Если мы используем значение 1, то полученная в результате сетка будет выглядеть как птица. Если мы используем значение 100, — сетка будет выглядеть как самолет. Если же мы используем значение 50, то полученная в результате сетка будет выглядеть как нечто среднее между птицей и самолетом. Чем ближе используемое значение к 1, тем больше сетка похожа на птицу. Аналогично, чем ближе значение к 100, тем больше сетка похожа на самолет.

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

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



Трассировка лучей


Трассировка лучей (ray-tracing) позволяет получить наиболее реалистичное изображение по сравнению с любым другим методом визуализации. Действительно, трассировка лучей известна как способ получения фотореалистичных и даже гиперреалистичных изображений. Трассировка лучей использует метод полностью отличный от методов, которые мы обсудили ранее. Алгоритм трассировки лучей автоматически вычисляет тени, отражения и преломления (другие методы визуализации не выполняют эти вычисления).

К сожалению, трассировка лучей очень медленна. Для получения одного изображения могут потребоваться часы и даже дни. Разумеется, трассировка лучей не подходит для графики в реальном масштабе времени и не поддерживается Direct3D. На рис. 2.26 приведено изображение, полученное методом трассировки лучей (с помощью программы POV-Ray).


Рис. 2.26. Сцена, полученная методом трассировки лучей



Требования к аппаратному обеспечению


Для работы с книгой подойдет любая машина с установленной системой Windows 95. С практической точки зрения, однако, лучше использовать компьютер с процессором Pentium и 16 мегабайтами памяти. Поскольку Visual C++, DirectX и программы из этой книги поставляются на CD-ROM, вам потребуется дисковод для чтения дисков CD-ROM. Также необходима видеокарта, которая может работать с DirectX.



Требования к читателю


Чтобы эффективно пользоваться этой книгой, вы должны быть программистом и знать язык C++. Нет необходимости быть экспертом по C++, но вам необходимо понимать такие особенности языка, как классы и виртуальные функции. Некоторые из этих идей становятся ясными при рассмотрении контекста в котором они используются, но лучше всего обратиться к классическим книгам по C++, таким как «Язык программирования C++» Бьерна Страуструпа и «Изучаем C++» Эла Стивенса.

Вам необходимо быть знакомым с Visual C++ и библиотекой MFC (Microsoft Foundation Classes). Мы будем использовать только часть возможностей MFC, так что особого мастерства не требуется.

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



Требования к программному обеспечению


Чтобы использовать Direct3D вам необходимы Visual C++ версии4.0 или выше, DirectX версии 2.0 или выше и Windows 95.



Трехмерные системы координат


Целью трехмерной графики является получение двухмерного представления трехмерной сцены. Двухмерное представление необходимо, потому что поверхность, на которой изображается сцена — в нашем случае экран компьютера — является двухмерной. Итак, трехмерная графика требует создания двух представлений одной и той же сцены: трехмерного представления, которое остается невидимым и двухмерного представления, которое отображается на экране. Сначала мы поговорим о невидимом, трехмерном представлении.

Представление объекта в трех измерениях может быть выполнено с использованием системы координат, состоящей из трех отдельных осей. Эти оси обычно называются X, Y и Z.

Существует два варианта трехмерной системы координат: левосторонняя и правосторонняя. Различаются они направлением оси Z. В левосторонней системе удаленные от зрителя объекты имеют большее значение координаты Z, в то время как более близкие объекты имеют меньшее значение координаты Z. В правосторонней системе координат ось Z имеет противоположное направление: удаленные объекты характеризуются меньшим значением координаты Z, а более близким объектам соответствует большее значение Z. Direct3D использует левостороннюю систему координат, так что далее в главе мы будем использовать именно ее. Увидеть левостороннюю систему координат вы можете на рис. 2.1. Стрелки указывают направления, в которых значения координат вдоль осей увеличиваются.


Рис. 2.1. Трехмерная система координат

Любая точка в пространстве может быть определена набором из трех значений. Эти значения показывают позицию точки вдоль каждой из осей и указываются в этой книге в угловых скобках, например, <1, 2, 3>. Числа в примере определяют положение точки вдоль осей X, Y и Z соответственно.



Удаление ненужных функций


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

OnActivate()

OnPaint()

OnSize()

OnEraseBkgnd()

В оконных приложениях эти четыре функции служили обработчиками событий. Функция OnActivate() вызывалась MFC когда приложение получало или теряло фокус. Мы использовали функцию OnActivate() чтобы оповещать Direct3D о поступлении сообщения WM_ACTIVATE с помощью функции HandleActivate() интерфейса Direct3DRMWinDevice. В полноэкранных приложениях подобное уведомление не требуется.

То же самое верно и для функции OnPaint(). В оконных приложениях мы использовали ее для вызова функции HandlePaint() интерфейса Direct3DRMWinDevice, что разрешало Direct3D выполнять обновление экрана. Поскольку обновление экрана мы теперь будем выполнять самостоятельно, функция OnPaint() нам больше не требуется.

Хотя функции OnActivate() и OnPaint() удалены, сообщения WM_ACTIVATE и WM_PAINT по-прежнему поступают и обрабатываются нашим приложением. По другому обстоит дело с сообщением WM_SIZE. Функция OnSize() была удалена потому, что размер окна приложения теперь не может быть изменен. Это полноэкранное приложение и функция изменения размера окна в нем не имеет смысла.

И, наконец, функция OnEraseBkgnd() была удалена потому, что ее задачей является очистка фона окна. Хотя наше приложение и создает окно, очистка его содержимого не требуется. Окно присутствует в основном для спокойствия GDI.



Управление количеством дочерних фреймов


Меню Children позволяет выбрать одно из четырех возможных значений, находящихся в диапазоне от одного до четырех. Подобно меню Depth для реализации каждого из пунктов меню применяются две функции. Ниже приведен код функций для первого пункта меню Children (задающего наличие у каждого фрейма одного потомка):

void MoleculeWin::OnChildren1() { numchildren = 1; CreateHierarchy(); }

void MoleculeWin::OnUpdateChildren1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(numchildren == 1); }

Эти функции аналогичны функциям меню Depth. Единственным отличием является использование переменной numchildren вместо curdepth.



Управление сценой


До сих пор мы обсуждали инициализацию Direct3D и создание сцены в нашей программе. Теперь мы обратим внимание на то, что происходит во время выполнения программы.



Управление скоростью обновления


Кроме всего прочего, объект Direct3DRM предоставляет функцию Tick(), которая управляет скоростью выполнения программы. Каждый раз, когда вы вызываете функцию Tick(), Direct3D обновляет анимированные элементы сцены и визуализирует результат. Более частые вызовы Tick() приводят к более частому обновлению сцены; если функция Tick() вызывается редко, программа замедляется. Функция Tick() получает единственный параметр, контролирующий скорость анимации сцены. В большинстве ситуаций достаточно задать значение 1.0, но, задавая во время выполнения другие значения, можно управлять скоростью работы программы.



Уровни кооперации


Степень контроля DirectDraw над видеокартой может быть настроена с помощью функции SetCooperativeLevel(). Уровень кооперации (cooperative level) определяет, в какой мере DirectDraw будет сотрудничать с другими приложениями. Для наших целей мы воспользуемся функцией SetCooperativeLevel() чтобы установить монопольный полноэкранный режим.



Установка приложений


Если у вас разрешен автозапуск программ с CD-ROM, то программа установки будет запущена автоматически, сразу после того, как вы поместите диск в ваш привод для чтения CD-ROM. Программа установки выполняет следующие действия:

Копирует исходный код и исполняемые файлы рассматриваемых в книге демонстрационных программ на жесткий диск вашего компьютера (вы можете выбрать диск и каталог).

Добавляет папку в меню Start, что упрощает запуск демонстрационных программ.

Устанавливает мастер Direct3D AppWizard (если установлен Visual C++).

Связывает X-файлы с программой Xpose, чтобы при двойном щелчке по X-файлу автоматически запускалась программа Xpose и отображала содержимое файла.

Кроме того, программа установки позволяет вам запустить демонстрационные программы непосредственно с CD-ROM.



Установка времени в анимации


Для установки текущей позиции в созданной анимации применяется функция SetTime(). Передаваемые функции SetTime() значения зависят от значений, применявшихся при зоздании ключевых кадров.

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

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



Установка времени в анимационных наборах


Аналогично интерфейсу Direct3DRMAnimation, интерфейс Direct3DRMAnimationSet предоставляет функцию SetTime(). В случае анимационного набора функция SetTime() устанавливает время для каждой анимации, входящей в набор.



Векторы


Вектор (vector), подобно точке, определяется тремя значениями, но вектор описывает направление и скорость, а не расположение в пространстве.

Возьмем для примера значения <0, 1, 0>. Если мы считаем эти числа описанием точки, тогда они указывают на место, расположенное на одну единицу выше начала координат (единицы выберите сами: сантиметры, мили, или что-то еще). Однако если мы считаем эти три числа описанием вектора, вместо местоположения мы получаем направление и скорость. В нашем случае направление — вверх и скорость 1. Представление векторов тройкой чисел несколько упрощено, поскольку в действительности для описания вектора необходимо шесть чисел: три для начальной точки и три для конечной. Это дает нам направление (ориентация второй точки относительно первой) и скорость (расстояние между двумя точками). Вектор может быть описан тремя числами, только если подразумевается, что его начальная точка совпадает с началом координат <0, 0, 0>.

Давайте взглянем на другой вектор: <2, 0, 0>. Этот вектор определяет направление вправо, потому что начинается в точке <0, 0, 0> и идет вправо вдоль оси X на две единицы. Поскольку вектор <2, 0, 0> представляется отрезком в два раза длиннее вектора <0, 1, 0>, скорость, определяемая вектором <2, 0, 0>, в два раза больше скорости, определяемой вектором <0, 1, 0>. На рис. 2.4 показаны два только что рассмотренных вектора.


Рис. 2.4. Вектор <0, 1, 0> и вектор <2, 0, 0>

Важно помнить, что векторы и точки — различные понятия. Точка определяет положение в пространстве, а вектор нет. Местоположение используется, чтобы определить вектор, но не наоборот (вектор не определяет местоположения). Векторы на рис. 2.4 расположены в начале координат, потому что они так представлены в цифровом виде, однако эти стрелки могли бы быть расположены в любом другом месте координатной системы.



Вершины


Вершины (vertices) представляют собой точки, которые используются для определения положения в трехмерном пространстве таких объектов, как грани и сетки (мы поговорим о гранях и сетках чуть позже). Вершины, подобно точкам, указывают местоположение, и сами невидимы. Однако Direct3D поддерживает режим, в котором вершины изображаются на экране точками. Этот режим имеет малое практическое значение, поскольку трудно оценить изображение, видя только вершины. Фактически, сцены нарисованные в этом режиме, напоминают головоломку в стиле «соедини все точки». (Великолепно — дайте мне карандаш!).



Вершины грани


Параметрами вершин можно манипулировать с помощью следующих функций интерфейса Direct3DRMFace:

AddVertex()

GetVertex()

GetVertexCount()

GetVertexIndex()

GetVertices()

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



Видеорежимы


Интерфейс DirectDraw позволяет нам определять и активировать видеорежимы. Для определения поддерживаемых видеорежимов предназначена функция EnumDisplayModes(). Найденный видеорежим может быть включен с помощью функции SetDisplayMode(). Функция RestoreDisplayMode() используется для возвращения видеокарты к тому видеорежиму, который был установлен до запуска нашего приложения. Позднее, при написании приложения FullScreen, мы воспользуемся этими функциями, чтобы наша программа могла включать любой поддерживаемый видеорежим.



Вращение


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

Мы можем использовать вектор, чтобы задать ось вращения, и числовое значение, чтобы указать, на сколько следует повернуть объект.

Итак, давайте вернемся к нашему исходному кубу и повернем его относительно оси Z на 45 градусов.

Вращение относительно оси Z означает, что куб повернется так, будто он проткнут осью Z и свободно поворачивается на ней только в одном направлении. Мы укажем это с помощью вектора <0, 0, 1> и числа 45. На рис. 2.16 показан результат выполнения этой операции вращения.


Рис. 2.16. Куб, повернутый на 45 градусов относительно оси <0, 0, 1>



Вспомогательные функции


Некоторые из упомянутых нами ранее функций не относятся к функциям Win32, MFC или Direct3D. Это вспомогательные функции, предоставляемые классом RMWin. Теперь мы рассмотрим эти функции подробнее.



и подробности нашей ранней истории


Происхождение человечества и подробности нашей ранней истории являются темой для множества дебатов. Первые страницы истории человечества нигде не записаны, но можно cделать некоторые предположения о том, как наши предки проводили время. Например, можно с уверенностью сказать, что они провели много времени создавая и используя различные инструменты.
Думая об инструментах сегодня, мы представляем отвертки и молотки, или, если речь идет о программистах, инструментальные средства программирования. Но первым инструментом был, вероятно, заостренный камень или кость, используемая в качестве ножа. Начав с этого, мы в конечном счете разработали копья, вертелы, шпаги и другие вещи. Мы создли меха для транспортировки и хранения пищи и воды и люльки, чтобы нести младенцев. Мы изобрели шитье, чтобы соединять шкуры вместе. Позже мы изобрели колесо. Бронзовый век стал свидетелем распространения металлических сковородок, мечей и щитов. Индустриальная революция навсегда изменила мир финансов, труда и потребления после распространения сложных машин, которые могли последовательно и надежно выполнять рутинную работу. Последнее столетие принесло автомобили, стиральные машины и консервные банки с ключом в миллионы домов по всему миру.
Дело в том, что нам досталось богатое и мощное наследство: наследство форм, рычагов, шкивов, блоков, и шасси, и существенной особенностью нашего технического наследия является трехмерность.

Выбор граней


Теперь мы знаем, как можно использовать порт просмотра для выбора сеток. Но что делать, если мы хотим выбирать отдельные грани сетки? Никаких проблем. Фактически, необходимый для этого код будет очень похож на код, который использовался при выборе сеток.



Выбор объектов


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

Выбор объектов сильно влияет на быстродействие. Он требует, чтобы Direct3D выполнил сортировку внутренних структур и данных буферизации. Это нетривиальная задача. Фактически, часто в момент выполнения операции выбора объекта наблюдается видимая задержка анимации.

С другой стороны, выбор объектов работает с точностью до пикселя. Direct3D может точно определить, какой объект был выбран, основываясь на местоположении указателя мыши. С помощью операции выбора объектов могут быть выбраны только видимые объекты.



Windows SDK против MFC


Windows SDK (Software Development Kit — комплект разработчика программного обеспечения) представляет собой набор функций, структур и макроопределений, позволяющий программистам писать приложения для Windows. В той или иной форме SDK присутствует в Windows, начиная с Windows 1.0 (да, когда-то была Windows 1.0). Написанный на C, SDK известен тем, что с ним трудно работать. Поскольку SDK не является объектно-ориентированным, его сложно расширять и программист вынужден уделять внимание всем мелким деталям программирования в Windows. Windows SDK поставляется вместе с Visual C++, но вы много потеряете, если ограничитесь программированием с использованием SDK. Настоящее сердце Visual C++ — это MFC.

MFC (Microsoft Foundation Classes) — это написанная на C++ библиотека классов, которая изолирует программистов от деталей Windows SDK. MFC действительно основа; набор гибких низкоуровневых объектов, которые можно расширить для применения практически в любой области. Многие классы MFC — просто тонкие оболочки вокруг конструкций Windows SDK. Сами по себе классы MFC не являются особенно внушительными или полезными, но они обладают замечательной расширяемостью. MFC также поставляется вместе с Visual C++.



X-файлы


X-файлы являются родным форматом файлов Direct3D. Х-файлы могут хранить множество сеток, иерархию фреймов и полные анимации. Текстуры не могут храниться в X-файлах.

В DirectX2, Direct3D поддерживал только текстовые версии X-файлов. DirectX 3 и следующие версии поддерживают как текстовый, так и двоичный формат.

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

X-файлы могут быть созданы утилитой CONV3DS или программой Direct3D с помощью функции Save() интерфейса Direct3DRMMeshbuilder. Посредством функции Save() можно создать только X-файлы содержащие сетки.

СОВЕТ Ограничения конструктора сеток. Если вы добавляете к конструктору сеток иерархию фреймов, а затем вызываете функцию Save(), полученный X-файл будет содержать все объекты иерархии, но как части одной сетки, а иерархия фреймов будет утеряна.



Z-буферизация


Z-буферизация использует буфер памяти для того, чтобы определить, какие поверхности расположены ближе к зрителю. Значения Z, которые хранятся в этом буфере, не обязательно соответствуют оси Z; они содержат информацию о «глубине» расположения каждой грани по отношению к зрителю. По мере вывода граней значения координаты Z точек сравниваются со значениями координаты Z из буфера. Если значения в буфере указывают, что выводимая поверхность ближе к зрителю, чем любые существующие, то новая поверхность будет наложена на старые. Если новая поверхность расположена дальше, чем какая-либо из существующих, то она не выводится.

Скорость визуализации может быть увеличена при помощи сортировки поверхностей от ближних к дальним (front-to-back). Это повышает производительность, потому что сначала выводятся объекты на переднем плане сцены, а невидимые поверхности не будут затронуты вообще. К счастью для нас, абстрактный режим Direct3D выполняет Z-буферизацию (включая сортировку) без всякого вмешательства с нашей стороны.



Z-буферизация: За и Против


Z-буферизация — один из самых простых и самых быстрых методов удаления невидимых поверхностей. Она обеспечивает точность визуализации до пикселя (некоторым алгоритмам это недоступно) и эффективно обрабатывает сложные сцены. Главный недостаток Z-буферизации — значительный объем памяти, который требуется для Z-буфера. Z-буфер должен быть, по крайней мере, такого же размера, какой имеет выходное изображение, и быть 32-разрядным. Например, для программы, работающей в режиме 800x600 с 16-разрядным Z-буфером, требуется почти мегабайт памяти только для одного Z-буфера.



Z-буфферизация


Одно из преимуществ использования Direct3D в оконной среде— автоматическое создание Z-буферов. В полноэкранных приложениях мы должны создать Z-буфер сами.

Z-буфер — это поверхность DirectDraw, обладающая возможностью Z-буфферизации. Подобно обычной поверхности, Z-буфер создается функцией CreateSurface() интерфейса DirectDraw. Как только Z-буфер создан и установлен, Direct3D может использовать его не требуя дальнейшего вмешательства.



Загрузка анимационных наборов


Анимационные наборы загружаются функцией Load() интерфейса Direct3DRMAnimationSet. Имя файла, передаваемое функции Load(), должно указывать на файл, содержащий завершенную анимированную сцену. Подобно другим функциям загрузки в Direct3D, функция Load() может выполнять чтение из файла, из ресурсов программы или из памяти.

Отдельные объекты анимации могут быть добавлены в анимационный набор или удалены из него с помощью функций AddAnimation() и DeleteAnimation().



Загрузка и сохранение


Функция Load() позволяет объектам конструктора сеток загружать сетки с диска, из ресурсов программы либо из памяти. Кроме того, функция Load() загружает любые назначенные сетке текстуры, если местоположение текстур указано в путях поиска Direct3D.

Функция Load() дает возможность выполнять функцию обратного вызова каждый раз при загрузке текстуры. Это позволяет изменять заданную по умолчанию загрузку текстуры и получать текстуры из заданных каталогов или из ресурсов программы.

Функция конструктора сеток Save() позволяет сохранить сетку как текстовый, двоичный или сжатый файл.



и отличаются друг от друга,


Хотя графические пакеты и отличаются друг от друга, существуют основные концепции и термины, являющиеся общими для всех. Концепции и термины, приводимые в этой главе, являются достаточно универсальными и применимы для большинства графических систем. В следующей главе мы познакомимся с Direct3D. Мы рассмотрим, как в Direct3D применяются базовые технологии, и чем он отличается от других графических пакетов.


Теперь, когда вы знаете, что представляют собой интерфейсы Direct3D и что они делают, можно приниматься за более веселое занятие — программирование. В главе 4 мы обсудим код приложения Direct3D.


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


Надеюсь, теперь вы узнали об источниках света и тенях в Direct3D все, что хотели. Возможно, вы даже узнали больше, чем намеревались!
В главе 7 мы расстанемся с использованием простейшей анимации (такой, как атрибуты вращения), поскольку изучим иерархии фреймов, ключевые фреймы, анимационные наборы и анимацию камеры.


Данная глава познакомила вас со способами анимации, поддерживаемыми Direct3D. Демонстрационные приложения были сделаны настолько простыми, насколько это возможно (но не настолько, чтобы стать совсем неинтересными). Однако, для реальных приложений они могут оказаться слишком упрощенными. Последовательность ключевых кадров, например, обычно не получают методом проб и ошибок. Гораздо чаще для разработки последовательности применяют специальные анимационные программы. Затем данные анимации экспортируют из анимационной программы и используются внутри приложения для создания реальной анимационной последовательности. Эта глава не посвящалась обсуждению способов разработки анимационных последовательностей. Вместо этого мы сконцентрировались на том, как использовать Direct3D API для реализации анимационной последовательности после того, как она разработана.
В главе 8 мы более подробно рассмотрим сетки. В частности, мы изучим анимацию подсеток. Это значит, что вместо того чтобы изменять положение всей сетки, мы будем анимировать ее отдельные вершины. Данная техника полезна для ряда приложений, например, для морфинга.


Я не могу считать эту главу законченной, пока не скажу несколько слов о том, как можно создавать собственные шаги трансформации. Итак, не вдаваясь в детали, вот несколько советов:
Простейший способ создания совместимых шагов трансформации — создать несколько экземпляров одной и той же сетки, а затем модифицировать каждый из экземпляров. Это утверждение верно в большинстве случаев, независимо от того, какую программу моделирования вы используете.
Сетки в сцене не должны быть связаны в иерархическую структуру. Присавивайте сеткам последовательные односимвольные имена, начиная с буквы «a».
Иногда, если для преобразования файлов 3DS в X-файлы используется утилита DirectX CONV3DS полезно использовать параметр «ignore frame transformation» (игнорировать трансформацию фрейма).
Вот и все, что я хотел рассказать вам о сетках. Мы долго изучали анимацию вершин и применили ее в трех демонстрационных программах. В главе 9 мы подробно изучим области просмотра. Возможно, для вас это окажется достаточно сложной темой.


Вполне возможно, что написанные вами приложения будут использовать порты просмотра Direct3D исключительно для отображения сцен. Тем не менее, если вам потребуется реализовать такие операции с портами просмотра, как трансфокацмя и выбор объектов, данная глава послужит хорошим отправным пунктом.
В следующей главе мы поговорим о совместном использовании DirectDraw и Direct3D для создания приложений, которые не используют окна Windows, а занимают весь экран.


Вот и все — это последняя глава книги. Я надеюсь, что вы узнали все, что хотели, а может быть и немного больше. Удачи!

Завершение работы


Инициализация Direct3D, создание сцен, и выполнение анимации в реальном времени настолько увлекательны, что не хочется останавливать программу, однако рано или поздно это придется сделать.



Зависимости COM-интерфейсов Direct3D


В главе3 обсуждались COM-интерфейсы Direct3D и обеспечиваемая ими функциональность. Во время обсуждения вы узнали, что некоторые интерфейсы должны создаваться раньше, а другие позже. Интерфейсы должны создаваться в определенном порядке. В этом разделе мы узнаем об этих зависимостях и о том, как они влияют на программы.



Зональное освещение


Зональный источник света, или прожектор (spotlight), испускает свет в форме конуса. При этом используется местоположение фрейма для определения точки испускания света (вершины конуса) и ориентация фрейма для определения направления светового потока.

Излучаемый источником свет описывается двумя углами: углом светового пятна (umbra angle) и углом зоны освещенности (penumbra angle). Угол светового пятна определяет конус, в котором свет имеет максимальную интенсивность. Угол зоны освещенности описывает больший конус, который определяет границу освещенной области. Между двумя этими конусами освещенность постепенно изменется от цвета источника света до черного (отсутствие света).

Угол светового пятна и угол зоны освещенности могут быть установлены с помощью функций SetUmbra() и SetPenumbra() интерфейса Direct3DRMLight.