Разработка компьютерных игр. Часть 6

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

Игры можно по праву считать очень сложными приложениями, требующими огромных трудовых затрат и усилий при создании. К сожалению, а может, и к счастью, еще не придуман тот универсальный конструктор, который позволит клепать Starcraft'ы, Spore и Doom'ы и превратить производство в обыкновенную штамповку, для обучения которой нужно будет готовить обычных операторов. Все дело в том, что технологии не стоят на месте, и чем они сложнее, тем лучше результаты. Как говорится в множестве литературы, библиотеки типа OpenGL и DirectX позволяют избавить программистов от большого пласта рутинной работы. Это правда. По существу, они являются некоей надстройкой, через которую программисты общаются с аппаратным обеспечением и, мало того, предоставляют инструментарий для создания виртуальных игровых миров.

Несколько слов об OpenGL

OpenGL по своей концепции содержит некоторое концептуальное отличие от DirectX, причем имеет под собой гораздо более глубокую историю, ведущуюся еще от разработчиков Silicon Graphics, и, в принципе, ни для кого не секрет, что раньше большая часть трехмерной анимации делалась именно на этих компьютерах. OpenGL расшифровывается как Open Graphic Library, то есть открытая графическая библиотека, которая может пополняться за счет различных фирм и отдельных разработчиков при условии соответствия определенному стандарту и прохождения тестов. Она достаточно интересна как в рассмотрении, так и в применении. Заложенные процедуры имеют все ключевые возможности, а именно работу с растровой и векторной графикой, двух- и трехмерным моделированием, текстуризацией, созданием сцен, управлением освещением и движением камер. Также есть весь необходимый набор эффектов — таких, как, например, прозрачность, туман, смешивание цветов и т.п. Она стоит несколько ближе к пакетам 3D-моделирования, потому как может генерировать трехмерные поверхности (сферы, многогранники), позволяет использовать кривые Безье и B-сплайны (NURBS-сплайны). Основной единицей информации для программиста, использующего OpenGL, является вершина, и нет строгой привязки к примитивам, что мы можем наблюдать в Direct3D. То есть он самостоятельно может указывать параметры соединения вершин: линиями, собирать их в различные многоугольники, их последующая триангуляция может производиться в автоматическом режиме либо с помощью специальных команд. Среди плюсов OpenGL стоит отметить надежность, практическую независимость от платформ и аппаратной части ПК (это немного спорно), стабильность отображения полученного результата на разных компьютерах (это называется переносимостью), а самый большой плюс — это быстрая инициализация и сравнительная простота разработки в ее рамках. Минус — медлительность и на самом деле уже достаточно слабая распространенность именно в геймдеве, хотя из разработчиков игр у OpenGL до сих пор остается множество приверженцев, даже среди высшей касты. Хотя медлительность для современных игр — очень плохая характеристика.

Команды в OpenGL можно условно разделить на две части: одни применяются для создания объектов, другие — для управления отображением их на экране. Набор базовых команд OpenGL включает примерно 300, причем в WinAPI вы можете получить доступ к ним из разных языков программирования — например, не только С, но и Delphi и Fortran. Кстати, именно последний указывает на то, почему OpenGL так полюбился в рамках систем автоматического проектирования (САПР) в том числе. OpenGL оснащен специальной библиотекой утилит (GLU), команды которой дополняют базовые функции OpenGL, и благодаря ей можно работать с теми же кривыми Безье и т.д. Есть и еще дополнительные библиотеки — например, AUX, которая позволяет обрабатывать события, обеспечивать вывод стандартных трехмерных объектов, управлять окнами, двойной буферизацией. То есть, по существу, OpenGL — это универсальное и мощное средство в умелых руках. Хотя у DirectX в определенном смысле есть некоторое неоспоримое преимущество. Говоря об аппаратной независимости OpenGL, мы немного слукавили, поскольку она подразумевает уровень включения (и, соответственно, поддержки) драйверов основного оборудования. Разработчики DirectX, а это не кто иной как Microsoft, в этом случае пошли по другому пути, а именно подогнули производителей видеокарт под себя. Смотрим…

Direct3D

Как уже было сказано, Direct3D — это низкоуровневый API (Application Programming Interface — интерфейс прикладного программирования), который стоит непосредственно между программистом и аппаратным обеспечением, то есть графическим устройством с его возможностями. Сама идея DirectX возникла неспроста. Тот, кто играл в компьютерные игры (из которых, кстати, очень много было создано на базе OpenGL) в середине 90-х прошлого столетия, могут вам рассказать, что при запуске игры нужно было обязательно зайти в Setup и выбрать драйверы аудио- и видеоустройств. Причем, если таковых не было в списке, то играли без звука и т.п. Технология DirectX стала неким посредником между разработчиками ПО и производителями железа, введя ключевую аббревиатуру HAL, о которой мы подробно поговорим позже. Соответственно, это сильно упростило ситуацию. То есть, производители комплектующих стали ориентироваться на стандарты, предлагаемые DirectX, "подгоняя" под них свое оборудование, а разработчиков перестали волновать вопросы совместимости с каждым отдельным устройством. Direct3D — это лишь часть технологии DirectX, которая отвечает за отображение трехмерной графики с выходом на аппаратные ускорители. Цепь следования взаимосвязей можно указать так: Приложение > Direct3D > HAL > Аппаратная часть (видеокарта). Теперь наступило время узнать, что такое HAL, и для чего он нужен.

HAL

HAL — это уровень абстрагирования Direct3D от оборудования (Hardware Abstraction Layer) — по существу, главный уровень взаимодействия с железом. Direct3D требует от видеокарты выполнения определенных команд и операций, предусмотренных в рамках его (а не карты) возможностей. Но он не может выходить напрямую к ресурсам аппаратуры, и это было бы бессмысленно. Ведь не секрет, что каждая видеокарта имеет свой набор поддерживаемых функций, собственные алгоритмы для выполнения той или иной задачи. Так вот, все возможности видеокарты встраиваются производителями в HAL, или, говоря простым языком, "прописываются" там ("прописываются" от слова "прописка"). Немного непонятно? ОК. Например, Direct3D необходимы возможности очистки экрана — она просматривает HAL, и, если там "кто-то прописан" в разделе очистки экрана, то происходит исполнение. Но какое? По алгоритмам, заложенным производителями видеокарты. То есть здесь царство Direct3D уже не действует. Причем у одних та же очистка экрана может происходить по одному алгоритму, у других — иначе: быстрее или медленнее — это уже Direct3D не касается. Это сравнимо с тем, что вы приходите в ЖЭС и обращаетесь в кабинет паспортиста. Если в нем есть человек, то вы отдаете ему паспорт, и он уже работает как может (качество его работы от вас не зависит). Если же в кабинете никого нет, то уж… увольте. Причем вы не можете самостоятельно сделать за него его работу, да у вас и нет полномочий внедряться во внутреннюю структуру кабинета. Если есть возможности, которые предлагает Direct3D, но они не поддерживаются устройством ("не прописаны" в HAL), то возникает ошибка — за исключением, пожалуй, только нескольких немногих случаев, например, обработки вершин. Она может выполняться и программно. То есть происходит переключение. Каждой возможности, предусмотренной в Direct3D, соответствует член данных или бит в структуре D3DCAPS9. Вы можете найти их полный список в документации к SDK. При инициализации экземпляра структуры D3DCAPS9 производится сверка с возможностями аппаратуры на конкретном компьютере — другими словами, того видеоадаптера, который установлен. Давайте покодируем — это весело! Например, проверим, может ли наше железо осуществить аппаратную обработку вершин. В документации по SDK находим, что это бит
D3DDEVCAPS_HWTRANSFORMANDLIGHT в члене данных D3DCAPS9::DevCaps. ОК, поехали.
Для начала инициализируем экземпляр D3DCAPS9:

HRESULT IDirect3D9::GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 *pCaps
);

В данном фрагменте кода Adapter указывает на физический видеоадаптер, который мы хотим проверить; DeviceType задает тип устройства, пока мы будем подразумевать аппаратное, а не программное, т.е. D3DDEVTYPE_HAL (об этом подробнее чуть позже); pCaps возвращает инициализированную строку с необходимым описанием возможностей видеокарты. Далее мы приступаем непосредственно к проверке и вносим информацию о возможностях первичного адаптера в структуру D3DCAPS9. Простым языком мы извлекаем данные согласно тому, что написано выше:

D3DCAPS9 caps;
d3d9->GetDeviceCaps(
D3DADAPTER_DEFAULT,
deviceType,
&caps);

В этом фрагменте D3DADAPTER_DEFAULT означает непосредственно первичный видеоадаптер; deviceType задает тип устройства, и строчка &caps возвращает заполненную структуру D3DCAPS9. Далее мы назначаем булеву переменную:

bool supportHWProc;

…и делаем проверку:

if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
supportHWProc = true;
}

Аппаратная поддержка обработки вершин есть или...

else
{
supportHWProc = false;
}

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

REF

Если есть возможности, которые реализуются только в некоторых видеокартах, а в арсенале разработчика такого оборудования не имеется, то тогда он использует ему на замену REF (reference rasterizer — вспомогательный растеризатор). Речь идет о программной эмуляции реализации возможностей Direct3D, которые на самом деле должны производиться аппаратно. Нужно это, прежде всего, для того, чтобы работа не останавливалась из-за отсутствия оборудования, то есть писать и проверять код можно и без оного с помощью REF. Отдельно стоит отметить, что это виртуальное устройство есть только в арсенале разработчика — пользователям оно недоступно. Кстати, работает очень медленно — это вам могут доказать те, кто использовал REF — например, эмулируя, шейдеры и так далее. В предыдущем коде мы использовали перечисление D3DDEVTYPE, в рамках которого задавался тип устройства. Так вот, константа, соответствующая аппаратному типу, называется D3DDEVTYPE_HAL, программному — D3DDEVTYPE_REF. Что касается обработки вершин — вернее, поддерживает ли устройство на аппаратном уровне обработку преобразований и расчет освещения — как мы уже говорили, Direct3D позволяет включать программную эмуляцию и для пользователей. Для этого есть два идентификатора, а именно D3DDEVCAPS_HWTRANSFORMANDLIGHT, который мы использовали в предыдущем коде для того, чтобы узнать, есть аппаратная поддержка или нет, и D3DCREATE_SOFTWARE_VERTEXPROCESSING. Эти константы являются предопределенными. В данном случае я не буду говорить тривиальные вещи о том, что аппаратная намного выгоднее, и почему.

COM

О Component Object Model (модели компонентных объектов) можно рассказывать много и долго. Например, я однажды подшутил над моим знакомым, подарив ему книгу с тремя большими буквами "COM" на обложке. Сначала он обрадовался, а потом оказалось, что это художественная литература, а в названии подразумевалась рыба-сом (Или это был справочник? Не помню. Вряд ли — пород сомов не так много). А если серьезно, то речь идет не об объектноориентированных моделях, кои у всех на слуху, а о компонентноориентированных (или даже интерфейсноориентированных). Как говорится, давайте дружить домами! Что такое COM, многим известно, с другой стороны, начинающим было рекомендовано самостоятельно изучить эту тему… но что- то мне подсказывает... Дело в том, что о COM написано очень много разного, и чаще всего достаточно запутанно. Многие авторы идут в исторические дебри от структурного и объектноориентированного программирования, а потом обрыв… и уже описывают IUnknown. Для примера я открыл одну весомую книгу по программированию, так там писавший человек сам запутался в рассуждениях. Можно сказать, завис на фразе: "судя по всему, интерфейс является направляющим механизмом для получения необходимых методов. Но реализовано это весьма хитро".

Итак, попробуем объяснить просто. Не так давно, а скорее уже давно, Microsoft предложила свою собственную технологию связывания и встраивания документов OLE. Она позволяла приложению содержать и управлять документами, которые были созданы другими приложениями, как своими собственными. В итоге мы получали некий общий комбинированный документ и могли воспринимать как единую сущность. Это немного отличается от объектноориентированного представления. Поэтому параллельно приведу пример с разветвленной банковской системой. По существу, она не зависит от каждого отдельного устройства, принимающего, например, платежи по кредитным картам, ПО, которое в нем функционирует и т.п. То есть это устройство подразумевает некий отдельный объект, задача которого — выдавать данные и воспринимать команды. То есть общение с ним происходит только на интерфейсном уровне. Соответственно оно должно быть встроено в общую систему и взаимосвязано с ней. Допустим, произошла поломка устройства — оно заменяется на другое, можно от другого производителя и с другой внутренней структурой; самое главное — чтобы оно было встраиваемо и выдавало нужный результат. Сама аббревиатура OLE не продержалась как таковая и вскоре была заменена на более емкую COM. Существует два понятия, а именно COM-объект и COM-интерфейс. Интерфейсом можно считать то соединительное звено, которое дается управляющему приложению для доступа непосредственно к COM-объекту. COM-объект — это компонент, являющийся конкретным экземпляром COM-класса; он может содержать множество функций, доступ к которым возможен через интерфейсы. COM-класс определяет интерфейсы и методы компонента. При создании компонента для его инициализации вызывается объект класса, после чего его интерфейсы предоставляются управляющему приложению.

:) Теперь простыми словами. Вернемся к примеру с банковской сетью. Допустим, вы устанавливаете новое устройство по приему платежей по кредитным картам. Оно должно инициализироваться как устройство определенного типа и предоставить доступ ко всем необходимым ресурсам, то есть производимым операциям. Но как определить, что устройство подключено, готово к взаимодействию и предоставляет определенные возможности? Для этого предусмотрен специальный механизм опроса, выраженный в интерфейсе IUnknown ("unknown" в переводе "неизвестный"), включение которого является обязательным для всех COM-объектов. Он содержит три метода: запроса и идентификации интерфейса QueryInterface(), увеличения счетчика ссылок AddRef(), уменьшения счетчика ссылок и освобождения занимаемой объектом памяти, если счетчик равен нулю. По существу, AddRef и Release управляют продолжительностью существования объекта — он существует, пока используется. Благодаря технологии COM Direct3D может использоваться независимо от языков программирования и быть совместимым с предыдущими версиями. На самом деле это удобное решение. Единственное, что стоит отметить отдельно: для эффективного использования DirectX углубленного изучения технологии COM не требуется, мало того, ссылки на СОМ-объект идут практически как на интерфейс, и по существу он воспринимается как класс С++. Правда, с одним ограничением, ведь не стоит забывать об IUnknown, от которого COM-интерфейсы наследуют функциональность. Поэтому к ним нельзя применять ключевое слово из С++ new и удалять оператором delete (вместо оного вызывается метод Release). Также, как вы смогли понять, управление памятью COM-объекты осуществляют самостоятельно. Для обозначения COM-интерфейсов в коде в начале используется заглавная "I" — например, IDirect3DSurface9 — либо же IDirect3D9, как в нашем листинге.

Кристофер, christopher@tut.by


Компьютерная газета. Статья была опубликована в номере 36 за 2008 год в рубрике технологии

©1997-2024 Компьютерная газета