WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     || 2 | 3 | 4 | 5 |   ...   | 6 |
-- [ Страница 1 ] --

Том Миллер Managed DirectX*9 Программирование графики и игр о м п * * э* > Предисловие Боба Гейнса Менеджера проекта DirectX SDK корпорации Microsoft SAMS [Pi] KICK START Managed DirectX 9

KICK START Управляемый DirectX9 расширяет возможности программирования игр и графики.

Книга позволяет изучить возможности использования Управляемого DirectX при разработке различных графических и мультимедийных приложений. В данной книге рассмотрены как основы программирования 3D графики, так и более сложные разделы, например, управление уровнями детализации mesh-объектов, использование высокоуровневого языка шейдеров и символьной анимации.

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

Материал книги "Managed DirectX 9" направлен в большей степени на непосредственное создание полнофункциональных мультимедийных приложений, нежели на изучение излишнего языкового синтаксиса.

Мы полагаем, что читатель этой книги уже знаком с языком С# (или Visual Basic.NET) и средой.NET Runtime.

Автор этой книги имеет огромный опыт разработки приложений с помощью DirectX с Управляемым кодом и является своего рода первоисточником информации в данной области.

ТОМ Миллер является ведущим разработчиком API Managed DirectX. В течении последних четырех лет он участвовал в разработке DirectX API, включая SDK и DirectX для Visual Basic. Перед этим он работал в группе разработчиков Visual Basic и Office корпорации Microsoft.

Содержание прилагаемого CD диска:

• DirectX 9 SDK • Исходные тексты программ на С# и Visual Basic.NET •.NET Runtime версии 1.1.

КАТЕГОРИЯ: Программирование.

ОХВАТЫВАЕТ: Программирование графики и компьютерных игр с помощью Управляемого DirectX 9.

УРОВЕНЬ пользователя: Средний.

Том Миллер DirectX с управляемым кодом Программирование игр и графика KICK START Издательский Дом «КомБук» Москва, Содержание Предисловие Об авторе Посвящается Будем признательны за ваши отзывы и пожелания! Введение ЧАСТЬ I ВВЕДЕНИЕ В КОМПЬЮТЕРНУЮ ГРАФИКУ Глава 1. Введение в Direct3D Глава 2. Выбор подходящего устройства Глава 3. Введение в технологию рендеринга Глава 4. Более совершенные технологии рендеринга Глава 5. Рендеринг Меsh-объектов Глава 6. Использование Управляемого DirectX для программирования игр ЧАСТЬ II ОСНОВНЫЕ КОНЦЕПЦИИ ПОСТРОЕНИЯ ГРАФИКИ Глава 7. Использование дополнительных свойств и возможностей Mesh-объектов Глава 8. Введение в ресурсы 6 Содержание Глава 9. Применение других типов Mesh Глава 10. Использование вспомогательных классов ЧАСТЬ III БОЛЕЕ СОВЕРШЕННЫЕ МЕТОДЫ ПОСТРОЕНИЯ ГРАФИКИ Глава 11. Введение в программируемый конвейер, язык шейдеров Глава 12. Использование языка шейдеров HLSL ' Глава 13. Рендеринг скелетной анимации ЧАСТЬ IV ЗВУК И УСТРОЙСТВА ВВОДА Глава 14. Добавление звука Глава 15. Управление устройствами ввода ЧАСТЬ V 2D ГРАФИКА Глава 16. Приложение Direct3D для 2D-графики Глава 17. Использование DirectDraw для рендеринга 2D-графики ЧАСТЬ VI ДОБАВЛЕНИЕ СЕТЕВЫХ ВОЗМОЖНОСТЕЙ Глава 18. Организация сети с равноправными узлами с помощью DirectPlay Глава 19. Создание сессии Client/Server Содержание Глава 20. Особенности более совершенного использования сетей Глава 21. Методы достижения максимального быстродействия ЧАСТЬ VII ПРИЛОЖЕНИЯ Приложение А. Использование сборок диагностики Приложение В. Воспроизведение музыки и видео Алфавитный указатель Оглавление Предисловие Об авторе Посвящается Участники работы Будем признательны за ваши отзывы и пожелания! Введение История Управляемого DirectX Включенные пространства имен Выбор программы ЧАСТЬ I. ВВЕДЕНИЕ В КОМПЬЮТЕРНУЮ ГРАФИКУ.... Глава 1. Введение в Direct3D Начало работы Устройство Direct3D Создание трехмерного треугольника Автоматический сброс устройства во время изменения размеров окна Создание освещения Состояния устройства и преобразования Свопинг-связки и рендеры Краткие выводы Глава 2. Выбор подходящего устройства Адаптеры системы Поддержка аппаратного устройства Проверка возможностей устройства Краткие выводы Глава 3. Введение в технологию рендеринга Использование вершинных буферов Текстурирование объектов Краткие выводы Оглавление Глава 4. Более совершенные технологии рендеринга Рендеринг с использованием примитивов различного типа Использование индексных буферов Использование буферов глубины или Z-буферов Краткие выводы Глава 5. Рендеринг «Меsh»-объектов Определение Mesh-объектов Использование материалов и освещения Использование Mesh-объектов для рендеринга сложных моделей Краткие выводы Глава 6. Использование Управляемого DirectX для программирования игр Выбор игры Программирование игры Добавление движущегося автомобиля в используемую сцену.... Добавление препятствий Последние штрихи Краткие выводы ЧАСТЬ П. ОСНОВНЫЕ КОНЦЕПЦИИ ПОСТРОЕНИЯ ГРАФИКИ Глава 7. Использование дополнительных свойств и возможностей Mesh-объектов Создание копий Mesh-объектов Оптимизация данных Mesh-объекта Упрощение существующих Mesh-объектов Объединение вершин в Mesh-объектах Создание нескольких объектов из одного Краткие выводы Глава 8. Введение в ресурсы Класс ресурсов Использование вершинных и индексных буферов Механизм Locking, блокировка используемых буферов Управление процедурой блокировки Использование текстурных ресурсов Блокировка текстур и получение описаний Краткие выводы 10 Оглавление Глава 9. Применение других типов Mesh Упрощение Mesh-объектов Управление степенью детализации, класс прогрессивных Meshes-объектов Рендеринг патч-объектов. Тесселяция объектов Примеры тесселированных объектов Краткие выводы Глава 10. Использование вспомогательных классов Рисование линий Отображение текста Рендеринг на поверхности Рендеринг текстур Environment Maps Краткие выводы ЧАСТЬ III. БОЛЕЕ СОВЕРШЕННЫЕ МЕТОДЫ ПОСТРОЕНИЯ ГРАФИКИ Глава 11. Введение в программируемый конвейер, язык тендеров Рендеринг треугольника с использованием программируемого конвейера Использование шейдеров для рендеринга, использование техник «TECHNIQUE» Использование программируемого конвейера для рендеринга mesh-объектов Использолвание языка HLSL для создания пиксельного шейдера Краткие выводы Глава 12. Использование языка шейдеров HLSL Использование простых формул для моделирования анимации... Объединение цветов текстуры с цветами поверхности Текстуры освещения Моделирование световых бликов Краткие выводы Глава 13. Рендеринг скелетной анимации Создание иерархической системы фреймов Загрузка объектов с анимацией Рендеринг анимированных объектов Краткие выводы Оглавление ЧАСТЬ IV. ЗВУК И УСТРОЙСТВА ВВОДА Глава 14. Добавление звука Включение пространства имен SOUND Загрузка и проигрывание статических звуков Использование ЗD-звука Управление слушателем Использование звуковых эффектов Краткие выводы Глава 15. Управление устройствами ввода Обнаружение устройств Использование клавиатуры Использование устройства мыши Использование игровых клавиатур и джойстиков Устройства обратной связи Краткие выводы ЧАСТЬ V. 2D ГРАФИКА Глава 16. Приложение Direct3D для 2D-графики Создание полноэкранного устройства отображения Рендеринг спрайтов Анимация спрайтов Краткие выводы Глава 17. Использование DirectDraw для рендеринга 2D-графики Создание полноэкранного устройства DirectDraw Анимация спрайтов Краткие выводы ЧАСТЬ VI. ДОБАВЛЕНИЕ СЕТЕВЫХ ВОЗМОЖНОСТЕЙ Глава 18. Организация сети с равноправными узлами с помощью DirectPlay Адреса DirectPlay Создание Р2Р-соединения Начало сеанса Использование модели событий Работа в сети 12 Оглавление Обработка потерянных сессий Краткие выводы Глава 19. Создание сессии Client/Server Создание выделенного сервера Создание соединения клиент-сервер Отслеживание моментов присоединения и выхода из сети Передача пакетов данных по сети Формирование отклика клиента Обработка отключения сервера Краткие выводы Глава 20. Особенности более совершенного использования сетей.- Модели событий и обработчики Определение пропускной способности и статистики сети Запуск приложений, использующих концепцию «Lobby» Создание лобби-приложения Добавление голосового чата Краткие выводы Глава 21. Методы достижения максимального быстродействия Преобразование типов в объекты Побочные эффекты моделей событий Эффективность методов Краткие выводы ЧАСТЬ VII. ПРИЛОЖЕНИЯ Приложение А. Использование сборок диагностики Перечисление всех опций в системе Проверка отдельных пунктов Приложение В. Воспроизведение музыки и видео Воспроизведение звукового файла Воспроизведение видео файла в отдельном окне Использование видео файла в качестве текстуры АЛФАВИТНЫЙ УКАЗАТЕЛЬ Введение DirectX API является мощным программным интерфейсом, позволя­ ющим разработчикам компьютерной графики писать быстродействую­ щие приложения, используя стандартизированный набор интерфейсов.

Первый выпуск DirectX совпал с выходом операционной системы Windows 95 и назывался «Games SDK» (средства SDK для разработки игр). С тех пор было выпущено более восьми новых версий API, однако, они предназначались только для разработчиков, использующих языки про­ граммирования С и C++, поэтому существовала целая группа разработ­ чиков, которые не имели удобного доступа к функциональным возмож­ ностям DirectX.

История Управляемого DirectX Первый выпуск Управляемого DirectX пришелся на 20 декабря (первая версия DirectX 9), однако проект продолжал находиться в разра­ ботке еще в течение некоторого времени. Позднее, во время разработки циклов для DirectX 8.1 и продолжению работы над DirectX для Visual Basic, мы начали прогон бета-версии Visual Studio.NET, и для нас сразу стала очевидна актуальность и перспективность данного направления. Я продолжил работу над прототипом «DirectX.NET», который впоследствии стал называться Управляемым DirectX.

Вначале предполагалось, что Управляемый DirectX будет столь же мощным, как и API. Ранее пользователи DirectX для Visual Basic не мог­ ли создавать приложения, сравнимые по качеству с приложениями DirectX API. Частично это было связано непосредственно со временем выполне­ ния процедур на Visual Basic, а частично с нехваткой ресурсов библиотек DLL.

В DirectX 8 мы попробовали устранить некоторые из этих проблем, удалив интерфейсные уровни для большинства характеристик API, Direct3D. Вместо имеющихся proxi DLL библиотек, которые сортирова­ ли или маршализировали данные, мы переместили Direct3D API непос­ редственно в библиотеку типов (OLE library). Правда, это совсем незна­ чительно улучшало характеристики, а также чрезмерно усложняло ин­ терфейс API, особенно для разработчиков на Visual Basic, на которых мы ориентировались.

Введение После выпуска DirectX 8 стало очевидно, что интерфейс API не со­ всем удобен в использовании. Примеры были сложны для понимания, и код не напоминал привычные приложения на Visual Basic.

Большинство разработчиков, использующих Visual Basic, нашли API слишком сложным в применении, а разработчики на C++ не видели при­ чин переключаться на API, поскольку для них это было неудобно и невы­ годно. До какого-то момента мы были интересны только домашним эн­ тузиастам, а студии-разработчики компьютерных игр даже не знали о нашем существовании. Поэтому было необходимо направить работу над нашим новым API на достижение быстродействия, а также удобства его использования, что явилось бы достаточно вескими причинами для пе­ рехода из C++ кода, на котором писались многие программы, на С#. Та­ ким образом, была намечена концепция для разработки Управляемого DirectX.

На Конференции Разработчиков Компьютерных Игр в Сан-Хосе в 2002 г. была представлена первая альфа-версия Управляемого DirectX на базе версии DirectX 8.1., написанная исключительно на С#.

В этой версии имелось большинство базовых компонентов DirectX, включая DirectMusic, который был исключен из окончательной версии Управляемого DirectX. Единственными компонентами DirectX, не пред­ ставленными в первой альфа-версии, были DirectDraw и DirectShow, глав­ ным образом вследствие того, что мы не решили окончательно, включать эти компоненты или нет.

Оглядываясь назад, на первую версию, можно без сомнения предпо­ ложить, что она была перспективной. Эта версия демонстрировала дос­ таточную оперативность Управляемого DirectX, и отзывы, которые мы получили от пользователей, были на удивление положительны, хотя ра­ бочие характеристики этого уровня были еще недостаточно высоки. В некоторых случаях мы смогли бы получить качество выполнения, соот­ ветствующее родному API, но даже при прокрутке простого сценария создания кубической текстуры версия с управляемым кодом существен­ но проигрывала в скорости по сравнению с версией на C++.

Мы потратили еще несколько месяцев на подготовку следующего ва­ рианта — первой бета-версии DirectX 9. Мы сосредоточили внимание на самых острых проблемах, добавили поддержку DirectDraw и удалили поддержку DirectMusic. Мы также включили в команду разработчиков, знакомых с С#. Полученная нами по результатам нашей работы обратная реакция была быстрой и однозначной — эксплуатационные характерис­ тики были замечательные. Управляемый DirectX отличался от других уп Введение равляемых API и напоминал скорее интерфейс COM API. Все компонен­ ты среды.NET Runtime остались общими, тогда как библиотеки Управ­ ляемого DirectX полностью изменились. Перед данным проектом мы ста­ вили две задачи: быстрота и простота в управлении. Стало очевидно, что мы не учли последнего.

Полная перестройка API началась сразу после выхода первой бета версии. Мы обсуждали необходимые вопросы и с командой разработчи­ ков среды.NET runtime, и с пользователями бета-версии. В результате мы собрали мнения всех заинтересованных источников и смогли спокой­ но выяснить и устранить недостатки данной версии. Я составил список параметров и требований, которых было необходимо придерживаться.

Некоторые изменения были достаточно просты в осуществлении, напри­ мер, константы или имена структур в DirectX (и СОМ вообще). Другие изменения были более сложными, в частности, добавление реестров к классам. Мы выпустили вторую бета-версию и с тревогой ждали обрат­ ную реакцию от наших пользователей.

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

За месяц до появления заключительной версии DirectX 9 один из ва­ риантов бета-версии был разослан пользователям по почте. Это был не полный комплект SDK, но он включал в себя Управляемый DirectX API.

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

Люди отправляли на соответствующие форумы и Web-сайты статьи, опи­ сывающие, как использовать API.

Когда в декабре вышла заключительная версия Управляемого DirectX 9, стало ясно, что мы добились желаемого результата.

Включенные пространства имен Управляемый DirectX API имеет достаточно большой список имен, namespaces, разделяющих функциональные возможности различных ком­ понентов DirectX. Имеется также универсальное пространство имен «Microsoft.DirectX», которое размещает базовые возможности. Полный список пространства имен, включенных в Управляемый DirectX, пере­ числен в таблице 1.

Введение Таблица 1. Пространство имен Управляемого DirectX Microsoft.DirectX Корневой каталог, содержит все общие коды Microsoft.DirectX.Direct3D Direct3D API графика, так же как вспомогательная библиотека D3DX Microsoft.DirectX.DirectDraw Графика DirectDraw API Microsoft.DirectX.DirectPlay Сетевой DirectPlay API.

Microsoft.DirectX.DirectSound Аудио DirectSound API.

Microsoft.DirectX.Directlnput Пользовательский вход в Directlnput API Microsoft.DirectX. AudioVideoPlayback API простого звукового и видео воспроизведения Microsoft.DirectX.Diagnostics Простая диагностика API Microsoft.DirectX.Security Контроль доступа на глубокие уровни DirectX Microsoft.DirectX.Security.Permissions Классы разрешения для защиты доступа в DirectX Как видно, этот список охватывает большинство функциональных возможностей, включенных в DirectX. В этой книге мы подробно рас­ смотрим приложения, связанные с пространством имен в Direct3D, и кос­ немся некоторых из перечисленных приложений.

Выбор программы Прежде чем начать описание работы с Управляемым DirectX, мы хо­ тели бы обратить внимание на некоторые моменты.

Первое, что нам понадобится для работы, — редактор исходного кода и среда разработки. Я бы рекомендовал Visual Studio.NET 2003, поддер­ живающий объекты Microsoft. Независимо от типа редактора, понадо­ бится версия 1.1 среды.NET runtime, которую вы можете установить с прилагаемого CD диска.

Введение Также понадобится установленный DirectX 9.0 SDK Update. Я бы ре­ комендовал DirectX 9.0 Software Development Kit Update, включенный в указанный CD диск. Там же можно найти множество примеров, а также документацию на Управляемый DirectX.

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

Однако, более сложные объекты требуют установки более совершенных видеокарт, способных работать с программами текстурирования, с вер­ шинными и пиксельными шейдерами. Карта GeForce3 является подходя­ щей для таких задач, однако, я бы порекомендовал карту, способную ра­ ботать с шейдером модели 2.0 (например, Radeon 9700 и выше).

Описывая принципы программирования Управляемого DirectX, мы подразумеваем, что читатель знаком с данной тематикой. В противном случае, книга может быть достаточно сложна для восприятия. Книга пред­ назначена для разработчиков компьютерной графики, имеющих навыки и опыт работы с DirectX, которые могут найти здесь дополнительную информацию относительно создания более продвинутых мультимедий­ ных приложений, использующих Управляемый DirectX. Код и алгорит­ мы, приводимые в тексте этой книги, написаны на языке С#, но на ком­ пакт-диске могут быть приведены версии процедур, написанные на Visual Basic.NET. Компакт диск содержит и другие исходники программ, как на языке С#, так и на Visual Basic.NET. Также DirectX 9 SDK Update можно загрузить с сайта http://msdn.microsoft.com.

Теперь мы можем приступить к рассмотрению принципов работы и программирования с помощью Управляемого DirectX.

ЧАСТЬ I ВВЕДЕНИЕ В КОМПЬЮТЕРНУЮ ГРАФИКУ Глава 1. Введение в Direct3D Глава 2. Выбор подходящего устройства Глава 3. Введение в технологию рендеринга Глава 4. Более совершенные технологии рендеринга Глава 5. Рендеринг «Меsh»-объектов Глава 6. Использование Управляемого DirectX для программирования игр 24 Часть I. Введение в компьютерную графику Глава 1. Введение в Direct3D Добавление трехмерной графики является одним из наиболее попу­ лярных аспектов мультимедийного приложения. Используя современные мощные системы обработки и графический процессор GPU, можно ото­ бражать самые реалистичные сцены в реальном масштабе времени. Уп­ равляемый Direct3D позволяет разработчикам компьютерных игр просто и быстро формировать на экране сложные элементы графики и мульти­ пликации. В этой главе мы охватим следующие моменты.

• Создание Direct3D устройств.

• Рендеринг или визуализация изображения в различных коорди­ натных системах (экранные и обычные координаты).

Добавление освещения к отображаемому полигону.

Начало работы Перед запуском своего первого приложения в среде Managed Direct3D необходимо сделать следующее:

1. Загрузить приложение Visual Studio.NET и создать новый проект (new project).

2. Выбрать в качестве пространства проекта или программы среду Visual C#, создать новое окно приложений (понадобится также место для рендеринга и стандартных форм Windows).

3. Придумать и записать имя нового проекта.

После того, как мы создали новый проект, необходимо убедиться, что к проекту добавлены необходимые ссылки на Управляемый DirectX, что позволит в дальнейшем использовать его компоненты. Для этого, исполь­ зуя меню проекта, кликните на команду добавления Add References и добавьте приложения MicrosoftDirectX и Microsoft.DirectX.Direct3D.

Пока это все, что может понадобиться нам на первом этапе.

Теперь, прежде чем начать использовать Управляемый DirectX, необ­ ходимо добавить два новых элемента в директиву «using», позволяющих автоматически идентифицировать используемые нами компоненты. Это можно сделать, открыв окно записи кода для главной формы Windows в вашем приложении (по умолчанию forml.cs) и добавив следующие стро­ ки в конце операторов «using»:

using Microsoft.DirectX;

using Microsoft.DirectX.Direct3D;

Последние описанные действия являются необязательными. Просто они позволяют не классифицировать каждый раз объекты перед их ис Глава 1. Введение в Direct3D пользованием. Итак, мы готовы к написанию первого приложения с по­ мощью Управляемого DirectX.

Устройство Direct3D Выполнение всех графических операций в Direct3D определяется клас­ сом устройства. Можно полагать, что этот класс аналогичен графическо­ му устройству и графической карте, установленной на компьютере.

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

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

public Device (System.Int32 adapter, Microsoft.DirectX.Direct3D.DeviceType deviceType, System.Windows.Forms.Control renderWindow, Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags, Microsoft.DirectX.Direct3D.PresentParameters presentationParameters ) Первый параметр adapter (адаптер) относится к тому физическому устройству, которое мы хотим классифицировать.

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

Нулевое значение для параметра adapter подразумевает установку уст­ ройства по умолчанию.

ПЕРЕГРУЗКИ ОПЕРАЦИЙ В КОНСТРУКТОРЕ УСТРОЙСТВ Вторая перегрузка для устройства идентична первой, за исключе­ нием параметра renderWindow, который принимает указатель IntPtr для неуправляемого окна. Последняя перегрузка принимает отдель­ ный указатель IntPtr, который является неуправляемым СОМ указа­ телем для интерфейса IDIRECT3DDEVICE9. Она может использовать­ ся при необходимости работы с неуправляемым приложением по­ средством управляемого кода.

Следующий параметр DeviceType (тип устройства) сообщает прило­ жению Direct3D, какой тип устройства мы хотим создать. Обычно, значе­ нием этого параметра является опция DeviceType.Hardware, подразуме­ вающая использование аппаратного устройства. Другой вариант значе 26 Часть I. Введение в компьютерную графику ния — опция DeviceType.Reference, которая позволяет использовать до­ полнительное или эмулированное устройство растеризации (rasterizer), реализующее все возможности Direct3D Realtime, но работающее чрез­ вычайно медленно. Эту опцию целесообразно использовать главным об­ разом для отладки программных задач или для проверки параметров при­ ложений, не поддерживаемых вашей видеокартой.

ИСПОЛЬЗОВАНИЕ ПРОГРАММНЫХ УСТРОЙСТВ Следует обратить внимание, что эмулированный rasterizer постав­ ляется только с DirectX SDK, при использовании DirectX Runtime эта опция будет недоступна. Последнее значение — DeviceType.Software позволяет использовать программный rasterizer.

Параметр renderWindow (окно рендеринга) связывает окно с создан­ ным устройством. Поскольку класс управления окнами содержит деск­ риптор окна, это облегчит использование соответствующего класса в ка­ честве окна рендеринга. Для этого параметра можно использовать фор­ му, панель, или любой другой производный класс. На данном этапе мы будем использовать только формы.

Следующий параметр behaviorFlags (флажки режимов) используется для управления режимами устройства после его создания. Большинство значений списка CreateFlags может быть объединено, что позволит при необходимости установить множественные режимы работы устройства.

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

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

Значение Windowed является логической переменной, определяющей, работает ли устройство в полноэкранном (значение

Значение SwapEffect используется для описания режима работы бу­ ферной подкачки. Если вы выбрали значение SwapEffectFlip, будет со­ здан вторичный буфер и произойдет копирование, независимо от состоя­ ния первичного буфера на данный момент. Значение SwapEffect.Copy сходно со значением Flip, но потребует от программиста установки зна Глава 1. Введение в Direct3D чения «1» для числа вторичных буферов. В данном случае мы выберем опцию SwapEffect.Discard, которая просто сбрасывает содержимое буфе­ ра, если он не готов к представлению.

Теперь, когда у нас имеется эта информация, мы можем создать уст­ ройство, вернувшись к нашему коду. Сначала мы должны определить объект, используемый нашим приложением, и добавить к нему новую переменную (private variable) «device». Для этого включите следующую строку в раздел определения класса:

private Device device = null;

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

Добавьте следующий код инициализации:

///

/// We will initialize our graphics device here /// public void InitializeGraphics)) ( // Set our presentation parameters PresentParameters presentParams = new PresentParameters() presentParams.Windowed = true;

presentParams.SwapEffeet = SwapEffect.Discard;

// Create our device device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);

Таким образом, мы определяем параметр предоставления presentParams, в котором устанавливаем описанные выше значения (Windowed и. SwapEffect), и затем создаем устройство. Нулевое значение параметра adapter подразумевает установку устройства по умолчанию. В результате мы создали фактическое аппаратное устройство, в противоположность устройству, эмулированному программными средствами.

Следует обратить внимание, что в рассмотренном коде использова­ лось ключевое слово «this», подразумевающее наше текущее окно ренде­ ринга. Поскольку наше приложение, и в особенности этот класс, являют­ ся своего рода винформой, мы просто используем его. Как уже упомина­ лось, мы возложили вершинную обработку на наш центральный процес­ сор.

Теперь осталось переписать основной метод для вызова процедуры инициализации InitializeGraphics:

28 Часть I. Введение в компьютерную графику static void Main() ( using (Forml frm = new Forml()) ( // Show our form and initialize our graphics engine frm.Show();

frm.InitializeGraphics();

Application.Run(frm);

I i Что мы здесь сделали? Сначала, мы добавили оператор или директиву.

«using» для создаваемой формы. Это гарантирует размещение нашей формы в каталоге, когда приложение оставляет область этого блока. За­ тем, мы добавили команду Show, для того чтобы гарантировать, что окно загружено и отображено (и таким образом создан хэндл окна) прежде, чем мы попытаемся создать наше устройство. Затем мы вызвали функ­ цию инициализации устройства и использовали стандартный алгоритм для нашего приложения. Теперь мы можем скомпилировать это первое приложение в Direct3D и запустить его.

Следует добавить, что созданное приложение довольно простое. Оно создает устройство, но фактически ничего с ним не делает, и глядя на это выполняемое приложение, мы не можем сказать, что оно как-то отлича­ ется от первоначально созданного пустого С# проекта. Необходимо пе­ реписать его и отобразить что-нибудь на нашем экране.

Классы Windows-form имеют встроенный метод, позволяющий отсле­ живать момент, когда необходимо обновить или «перерисовать» окно — это метод OnPaint (можно также использовать приложение Paint). Доба­ вим этот метод в наш код рендеринга. Нет смысла делать сейчас какие-то сложные вещи, попробуем просто очистить окно и заполнить его чистым цветом. Добавьте следующий код к определению класса:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Clear(ClearFlags.Target, System.Drawing.Color.CornflowerBlue, l.Of, 0);

device.Present();

} Для того чтобы заполнить наше окно чистым цветом, воспользуемся процедурой Clear. В этом случае мы используем один из предварительно определенных цветов, а именно CornflowerBlue. Первый параметр мето­ да Clear определяет, что именно мы хотим очистить, в данном примере это будет текущее окно.

Глава 1. Введение в Direct3D Вторым параметром является цвет, используемый при очистке наше­ го окна. После того как устройство очищено, мы можем обновить состо­ яние дисплея. Данную процедуру будет выполнять метод представления Present. Существует несколько различных вариантов данного метода, один из них, показанный здесь, использует всю область устройства. Другие перегрузки мы обсудим позже.

Запустив приложение, мы увидим окно, заполненное синим цветом (цвет василька). Можно изменять размеры окна или разворачивать его на весь экран, и каждый раз окно будет заполняться синим цветом. Несмот­ ря на то, что наше новое устройство уже что-то из себя представляет, созданное приложение все еще довольно скучно. Можно было сделать то же самое, просто установив цвет фона формы в конструкторе програм­ мы. Теперь попробуем нарисовать еще что-нибудь.

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

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

К счастью, Управляемый DirectX уже имеет встроенную конструкцию для хранения и обработки информации об указанных треугольниках. В составе имен Direct3D существует класс вершин CustomVertex, который размещает используемые в Direct3D различные сборки вершин в форма­ те «CustomVertex». Структура вершинного формата содержит данные в понятном для Direct3D виде. Мы коснемся этого вопроса позже, а сейчас будем использовать для нашего треугольника структуру TransformedCo lored — преобразование цветов. Эта структура сообщает Direct3D runtime, что наш треугольник не требует каких-либо преобразований, например, вращения или перемещения, поскольку мы будем определять или уста­ навливать его положение посредством экранных координат. Данная струк­ тура включает цветовые параметры для каждой вершины треугольника.

Вернемся к методу OnPaint и добавим следующий код после вызова Clear:

CustomVertex.TransformedColored[j verts = new CustomVertex.TransformedColored[3];

verts[OJ.SetPosition(new Vector4 (this. Width / 2. Of, 50. Of, 0.5f, 1.0 f).) ;

verts[0].Color = System.Drawing.Color.Aqua.ToArgb();

verts[lJ.SetPosition(new Vector4(this.Width - (this.Width / 5.Of), this.Height (this.Height / 5.Of), 0.5f, 1.Of));

30 Часть I. Введение в компьютерную графику verts [I].Color = System.Drawing.Color.Black.ToArgb();

verts[2].SetPosition(new Vector4(this.Width / 5.Of, this.Height (this.Height / 5.Of), 0.5f, l.Of));

verts [2].Color = System.Drawing.Color.Purple.ToArgb();

Так как каждый элемент массива, который мы создали, представляет одну точку в нашем треугольнике, таким образом будет создано три эле­ мента. Затем, используя структуру Vector4, мы вызываем метод SetPosition для каждого элемента массива. Местоположение преобразованной вер­ шины определяется значениями X и Y в координатах экрана (относи­ тельно начала координат окна 0,0), а также значениями глубины Z и rhw (обратная величина «homogenous w»). Последние два значения для на­ шей выборки игнорируются. Структура Vector4 очень удобна для зада­ ния информации о треугольнике.

Далее мы устанавливаем цвет каждой вершины. Следует обратить внимание на то, что мы должны вызвать используемый для этого метод задания стандартных цветов ToArgb. В приложении Direct3D подразуме­ вается, что цвета будут определяться 32-разрядными целыми числами, и данный алгоритм преобразует базовый цвет в этот формат.

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

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

device.BeginScene ();

device.VertexFormat = CustomVertex.TransformedColored.Format;

device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);

device.EndScenef);

Здесь команда BeginScene уведомляет Direct3D о том, что мы собира­ емся что-то нарисовать, и определяет его «готовность к этому». Далее, мы должны сообщить, что именно мы собираемся нарисовать. Метод VertexFormat устанавливает для приложения Direct3D runtime использу­ емый нами в дальнейшем формат непрограммируемого конвейера (фор­ мат «fixed function pipeline»). В данном случае для рисования мы исполь­ зуем преобразованный и цветной вершинный формат. О непрограммиру­ емом конвейере мы расскажем позднее.

Функция или метод DrawUserPrimitives определяет место, где разме­ щается рисунок. Что же означают параметры этой функции? Первый па­ раметр — это тип примитива, который мы планируем рисовать. Суще Глава 1. Введение в Direct3D ствуют различные примитивы, которые нам доступны, но сейчас, для про­ стоты, мы будем рисовать набор треугольников TriangleList. Второй па­ раметр — число треугольников, которые мы хотим нарисовать, или дру­ гими словами, общее число вершин, разделенное на три. Для случая од­ ного треугольника, это значение равно «1». Последний параметр этой функции означает, какие данные Direct3D будет использовать при рисо­ вании. В нашем случае это вершины.

Последняя команда EndScene сообщает приложению Direct3D runtime об окончании процедуры рисования. Эта команда должна присутство­ вать всегда, если использовалась команда BeginScene.

Теперь можно компилировать и запускать наше новое приложение.

Обратите внимание, что на цветном фоне теперь нарисован треугольник.

Цвета вершин треугольника задаются нами в программе, а на изображе­ нии цвета внутри треугольника плавно изменяются от одного к другому.

Direct3D автоматически интерполирует цвета. Для практики попробуйте изменить базовые цвета и почувствуйте результат.

Если вы уже опробовали работу нового приложения, то, наверное, за­ метили некоторые особенности.

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

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

this.Invalidate();

Может показаться, что мы сломали наше приложение! В результате выполнения этой командной строки мы видим главным образом пустой экран, и наш мерцающий треугольник. И этот эффект проявляется более отчетливо, когда вы изменяете размеры вашего окна. Что же произошло?

Оказывается, Windows пытается обработать ситуацию и перерисовывает текущую форму нашего окна после аннулирования, окрашивая и вне­ шнюю сторону объекта. Его легко зафиксировать, изменяя параметр style созданного нами окна. Для этого в конструкторе формы замените сег­ мент «TODO» следующей строкой:

this.SetStyLe(ControlStyles.ALlPaintinglnWmPaint | ControlStyles.Opaque, true);

Часть I. Введение в компьютерную графику Теперь, когда мы запускаем приложение, все работает нормально.

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

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

Если вспомнить, что раньше, когда мы формировали данные нашего треугольника, мы использовали нечто, называемое преобразованными координатами (transformed coordinates). Эти координаты, как известно, лежат в пространстве экрана и легко определяются. А что если использо­ вать непреобразованные координаты? Они и составляют наибольшую часть сцены в современной 3D-иrpe.

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

Вначале мы изменим данные нашего треугольника, чтобы использо­ вать один из типов непреобразованных вершинных форматов. В этом случае нас волнует местоположение и цвет нашей вершины, поэтому мы выбираем команду CustomVertex.PositionColored. Замените ваш код для данных треугольника на следующий:

CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[3];

verts[0].SetPosition(new Vector3(0.0f, l.Of, l.Of));

verts[0].Color = System.Drawing.Color.Aqua.ToArgb();

verts[l].SetPosition(new Vector3(-l.Of, -l.Of, l.Of));

verts[1].Color = System.Drawing.Color.Black.ToArgb();

verts[2].SetPosition(new Vector3(l.Of, -l.Of, l.Of));

verts [2].Color = System.Drawing.Color.Purple.ToArgb();

Измените также свойства вершинного формата VertexFormat:

.лава 1. Введение в Direct3D device.VertexFormat = CustomVertex.PositionColored.Format;

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

Легко заметить, что мы переписали наши данные для использования вме­ сто них структуры PositionColored. Эта структура будет сохранять вер­ шину в пространстве внешних координат, а также цвет каждой вершины.

Поскольку вершины не являются преобразованными, мы используем класс Vector3 вместо класса Vector4, который мы использовали с преоб­ разованным объектом.

Элементы Vector3 структурируют карту непосредственно в координа­ тах мирового пространства (координаты X, Y, и Z). Мы также должны удостовериться, что приложению Direct3D известно о том, что мы изме­ нили тип рисуемых данных. Таким образом, чтобы использовать новую непреобразованную и цветную вершину, мы изменяем наш непрограм­ мируемый конвейер («fixed function pipeline»), модифицируя свойства вер­ шинного формата VertexFormat.

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

Камера управляется с помощью двух различных преобразований. Каж­ дое преобразование определяется матрицей 4x4, которую вы можете пе­ реслать в Direct3D. Преобразование проекции используется для опреде­ ления того, как сцена проецируется на экран. Один из самых простых способов создания матрицы проекции состоит в использовании функции PerspectiveFovLH в классе Matrix.

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

Что означает левая система координат, и какое это имеет значение? В обычной Декартовой системе координат положительная ось X направле­ на слева направо, тогда как положительная ось Y направлена вверх. Тре­ тьей координатой является ось Z. В левой системе координат положи­ тельная ось Z направлена от вас, в то время как в правой системе коорди­ нат, положительная ось Z направлена к вам. Это легко запомнить, если направить пальцы руки по направлению оси X, а большой палец — по направлению оси Y, как показано на рис. 1.1. Другими словами, если, глядя из конца вектора Z, поворот от оси X к оси Y осуществляется про : Зак. 34 Часть I. Введение в компьютерную графику тив часовой стрелки — это правая система координат, если по часовой стрелке — левая система координат.

Левая система координат Правая система координат Y *-Х Рис. 1.1. Трехмерная система координат Приложение Direct3D использует левую систему координат. Если вы пишете код для правой системы координат, необходимо сделать две вещи.

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

вы можете сделать это путем перестановки членов М31, М32, МЗЗ и М матрицы вида. Затем вы можете использовать версию RH матричной фун­ кции для построения матрицы правой системы.

Теперь, создавая новое приложение, мы будем использовать только левую систему координат, с которой работает Direct3D. Пример для на­ шей функции матрицы проекции имеет вид:

public static Microsoft.DirectX.Matrix PerspectiveFovLH ( System.Single fieldOfViewY, System.Single aspectRatio, System.Single znearPlane, System.Single zfarPlane ) Преобразование проекции используется для описания сцены, пред­ ставленной в виде усеченной пирамиды (frustum), где внутренняя часть пирамиды является просматриваемой областью нашей сцены. Два пара­ метра в нашей функции, ближняя и дальняя плоскости (znearPlane и zfarPlane) описывают пределы этой пирамиды, причем дальняя плоскость является основанием, а ближняя плоскость проходит по месту отсечения вершины пирамиды, рис.1.2. Параметр поля зрения определяется углом при вершине пирамиды, рис.1.3, а форматное соотношение сторон — аналогично форматам телевидения, например, широкоформатное теле­ видение имеет соотношение 1.85. Можно понимать этот параметр как отношение ширины изображения к высоте. Следует отметить, что Глава 1. Введение в Direct3D Direct3D прорисовывает только те объекты, которые заключены внутри усеченной пирамиды.

видимое пространство передняя задняя плоскость плоскость отсечения отсечения Рис. 1.2. Визуализируемое видимое пространство местоположение > Z камеры Рис. 1.3. Определение поля зрения Итак, теперь попробуем добавить камеру к нашей сцене, поскольку без камеры мы не сможем выполнять описанные выше преобразования:

public static Microsoft.DirectX.Matrix LookAtLH(Microsoft.DirectX.Vector cameraPosition, Microsoft.DirectX.Vector3 cameraTarget, Microsoft.DirectX.Vector cameraUpVector ) 36 Часть I. Введение в компьютерную графику Эта функция достаточно очевидна. Требуются три параметра, кото­ рые описывают свойства камеры. Первый параметр — положение каме­ ры во внешней системе координат. Следующий параметр — местополо­ жение того объекта во внешней системе координат, на который должна смотреть камера. Последний параметр — направление камеры, в нашем случае «Up» — вверх.

С описанием преобразования проекции и преобразования вида, Direct3D теперь имеет достаточно информации, чтобы отобразить наш недавно созданный треугольник. Давайте изменим наш код, чтобы ото­ бразить некий объект. Мы добавим новую функцию SetupCamera (уста­ новка камеры) в нашу процедуру OnPaint сразу после вызова очистки Clear. Тело этой функции будет включать следующее:

private void SetupCamera() device.Transform.Projection = Matrix.PerspectiveFovLHl(float)Math.PI / 4, this.Width / this.Height, i.Of, 100.Of);

device.Transform.View = Matrix.LookAtLH(new Vector3(0,3, 5.Of), new Vector3(), new Vector3(0,1,0));

i Как вы можете видеть, мы создаем матрицу преобразования проек­ ции для нашего устройства, чтобы обозначить приложению Direct3D об­ ласть наблюдения. Затем мы определяем нашу матрицу вида и задаем информацию о положении камеры. Добавьте обращение к этой функции в нашу процедуру OnPaint после вызова очистки Clear и запустите еще раз наше приложение.

Что мы видим теперь? Мы нарисовали наш треугольник, но теперь он оказался весь черный, даже при том, что мы определили цвета этого тре­ угольника. Еще раз, проблема заключается в различии между предвари­ тельно преобразованным треугольником (pretransformed), который мы нарисовали в первом приложении, и нашим непреобразованным треу­ гольником теперь. В непреобразованной среде приложение Direct3D по умолчанию использует освещение, чтобы определить конечный цвет пик­ села каждого примитива в сцене. Так как мы не используем никакого освещения в нашей сцене, нет никакой внешней засветки на нашем треу­ гольнике, и поэтому он кажется черным. Если мы уже определили цвет и хотим, чтобы наш треугольник появился, можно пока просто выключить освещение в сцене, вы можете выполнить это, добавив следующую стро­ ку в конце вызова функции SetupCamera:

device.RenderState.Lighting = false;

Глава 1. Введение в Direct3D ИСПОЛЬЗОВАНИЕ СОСТОЯНИЙ РЕНДЕРИНГА В УСТРОЙСТВЕ Существует много состояний рендеринга, которые могут исполь­ зоваться для управления различными сценами в конвейере ренде­ ринга. Мы обсудим часть из них в последующих главах.

Теперь при запуске приложения мы увидим треугольник, напоминаю­ щий наш первый, предварительно преобразованный треугольник. Мо­ жет показаться, что, выполнив немалое количество действий, мы верну­ лись в ту же самую точку, где мы находились еще до перехода к нашим непреобразованным треугольникам. Какие реальные преимущества мы получили от проведения этих изменений? Главное заключается в том, что теперь мы имеем треугольник в реальном трехмерном пространстве, это предпочтительнее, чем просто рисунок в координатах экрана.

Для более значимого восприятия того, что мы действительно имеем трехмерное изображение треугольника, попробуем вращать его. Как же это можно сделать? Это достаточно просто, мы должны изменить преоб­ разование пространства.

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

device.Transform.World - Matrix.Rotations((float)Math.PI / 6.Of);

Эта строка сообщает приложению Direct3D о типе используемого пре­ образования для каждого рисуемого объекта. В данном случае это вра­ щение нашего объекта относительно оси Z. Угол поворота задается стро­ го в радианах. В библиотеке функций Direct3DX, которую мы добавим позже к нашему проекту, существует вспомогательная функция Geornetry.DegreeToRadians.

Пока мы выбрали произвольный угол вращения, чтобы просто пока­ зать сам эффект. В результате мы видим на экране наш треугольник, по­ вернутый вокруг- оси Z.

Теперь попробуем вращать треугольник непрерывно.

38 Часть I. Введение в компьютерную графику Для этого произведем следующее преобразование пространства:

device.Transform.World = Matrix.RotationZ((System.Environment.TickCount / 450.Of) / (float)Math.PI);

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

Движение кажется немного прерывистым, но это вызвано дискретно­ стью параметра TickCount. Данный параметр в системном классе пред­ ставляет из себя некоторый дополнительный интерфейс для метода GetTickCount в интерфейсе приложений Win32 API, который имеет врем менную разрешающую способность приблизительно 15 миллисекунд. Это означает, что значение, приведенное здесь, изменяется с приращением приблизительно 15 мс, что и вызывает это прерывистое поведение. Мы легко можем сгладить вращение при наличии нашего собственного счет­ чика, использующего свое приращение, отличное от TickCount. Добавь­ те новое значение переменной угла в виде числа с плавающей точкой.

Тогда изменение вашего преобразования будет иметь следующий вид:

device.Transform.World = Matrix.RotationZ(angle / (float)Math.PI);

angle += O.lf;

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

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

Измените процедуру преобразование пространства следующим обра­ зом:

device.Transform.World = Matrix.RotationAxis(new Vector3(angle / ((float)Math.PI * 2.Of), angle / ((float)Math.PI * 4.Of), angle / ((float)Math.PI * 6.Of)), angle / (float)Math.PI);

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

Увы, теперь наш треугольник начинает вращаться вокруг своих осей, постоянно исчезая на мгновение, а затем вновь появляясь. Однако, мы уже сталкивались с похожей ситуацией, связанной с освещением неви­ димых поверхностей.

При отображении фигур отдельные поверхности или стороны объек­ та оказываются вне поля зрения камеры и не прорисовываются. Такой процесс называется back face culling, другими словами, устранение не­ видимых поверхностей (при двумерном изображении трёхмерных объек­ тов). Теперь для приложения runtime осталось определить, насколько и какая часть поверхности попадает в поле зрения камеры. Быстрый взгляд на опции culling в Direct3D дает хорошую подсказку. Имеются в наличии три опции culling: none, clockwise и counterclockwise (соответственно:

никак, по часовой стрелке и против часовой стрелки). В случае выбора варианта по часовой стрелке или против часовой стрелки, исходные объек­ ты, чьи вершины расположены в противоположном порядке, не рисуют­ ся.

Глядя на наш треугольник, можно видеть, что вершины располагают­ ся против часовой стрелки, рис. 1.4. Следует отметить, что такой порядок в DirectX выбирается по умолчанию.

Рис. 1.4. Порядок вершинных доменов Итак, когда мы знаем, как работает алгоритм исключения невидимой поверхности, стало очевидно, что для нашего простого приложения мы просто не нуждаемся в тех объектах, которые будут исключены. Суще­ ствует простое состояние рендера (render state), которое управляет режи­ мом отбора и устранения. Добавьте следующую строку к нашему вызову функции SetupCamera:

Часть I. Введение в компьютерную графику device.RenderState.CullMode = Cull.None;

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

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

Автоматический сброс устройства во время изменения размеров окна Любой, кто когда-либо писал приложение Direct3D на языке C++ или DirectX для Visual Basic, знает, что при изменении окна вашего приложе­ ния устройство должно сброситься или перезапуститься, иначе прило­ жение Direct3D продолжит рендеринг используемой сцены с тем же са­ мым пространственным разрешением, что и раньше, и в результате изоб­ ражение будет скопировано (и соответственно вытянуто, чтобы запол­ нить окно) в измененное окно.

Управляемый DirectX способен контролировать процедуру изменения форм окон и автоматически перезапускать устройство при изменении окна. Для этого существует опция, называемая DeviceResizing (измене­ ние окна устройства), формирующая код или процедуру автоматическо­ го сброса устройства. Устанавливая данную опцию и определяя значе­ ние «true» для отмены (Cancel) класса обработчика событий EventArgs, вы можете сбросить этот параметр в момент изменения окна. Добавьте следующую функцию к вашему примеру:

private void CancelResize(object sender, CancelEventArgs e) e. Cancel = true;

} Как можно видеть, эта функция подтверждает отмену действия. Да­ лее необходимо добавить этот обработчик событий к нашему устройству.

Добавьте следующую строку в конце метода создания устройства:

device.DeviceResizing += new CancelEventHandler(this.CancelResize);

Теперь запустите приложение еще раз и после того, как оно начнет прокручиваться, максимизируйте окно. Картина, которую вы видите да­ леко не идеальная, треугольник находится в том же самом положении, но теперь его грани имеют неровные края. Необходимо удалить последние Глава 1. Введение в Direct3D две секции добавленного нами кода. Заданный по умолчанию режим Управляемого DirectX отрабатывает процедуру изменения размеров уст­ ройства, и мы можем воспользоваться преимуществом этого режима.

Создание освещения Теперь, когда мы создали вращающийся треугольник, мы можем до­ бавить освещение. Мы уже кратко упомянули об освещении ранее, когда наш треугольник стал полностью черным после того, как мы использо­ вали непреобразованные треугольники. Фактически в тот момент мы полностью выключили освещение в нашем сценарии. Первое, что мы должны сделать, это — включить освещение, для этого необходимо ус­ тановить значение «true» в операторе освещения:

device.RenderState.Lighting = true;

Вы могли бы просто удалить эту строку, поскольку для устройства по умолчанию установлено значение «lighting on» — освещение включено, но оставим это для наглядности. Запуская приложение теперь, легко за­ метить, что мы опять вернулись к черному вращающемуся треугольнику.

Нам необходимо вначале определить свет и уже потом включить его.

Обратите внимание, что класс устройства имеет прикрепленный «свето­ вой» массив, каждый член которого определяет различные свойства све­ та. Если мы хотим «настроить» первоначальный свет в нашей сцене и включить его, следует добавить следующий код в наш метод OnPaint сра­ зу после кода определения нашего треугольника:

device.Lights[0].Туре = LightType.Point;

device.Lights[0].Position = new Vector3();

ievice.Lights[0].Diffuse = System.Drawing.Color.White;

device.Lights[0].Attenuation() = 0.2f;

device.Lights[0].Range = 10000.Of;

device.Lights[0].Commit));

device.Lights[0].Enabled = true;

Вначале определяется, какой тип освещения мы хотим отобразить. Мы выбрали точечный источник света, который излучает во всех направле­ ниях одинаково, подобно свече или спирали лампы накаливания. Суще­ ствует также направленное или параллельное освещение. Например, на­ ходясь на Земле, можно думать о Солнце, как о направленном источнике света, если учесть расстояние от Солнца до Земли (хотя в действительно­ сти. Солнце скорее является точечным источником, испускающим расхо­ дящиеся в пространстве световые волны). Направленный свет характе глвчется только направлением и цветом и исключает все другие свето 42 Часть I. Введение в компьютерную графику вые коэффициенты, как-то: затухание и диапазон, и является наиболее предпочтительным в вычислительном аспекте его использования. Мож­ но отметить еще один тип светового источника — световое пятно (тип источника, излучающего конический пучок световых лучей), который, как следует из его названия, используется для получения светового пят­ на на объекте подобно тому, что вы наверняка часто видели на концерте при освещении сцены. Учитывая большое количество коэффициентов в таком варианте освещения (местоположение, направление, конический угол и так далее), такое освещение более громоздко при использовании его в вычислительных операциях.

Продолжим. Далее мы хотим установить положение нашего точечно-, го источника освещения. Так как координаты центра нашего треугольни­ ка составляют (0, 0, 0), мы можем там же расположить и наш источник света. Это можно сделать с помощью конструктора параметров parameterless класса Vector3. Мы устанавливаем диффузный компонент осветителя diffuse для получения рассеянного белого света, так чтобы поверхность нормально освещалась. Далее мы задаем параметр затуха­ ния attenuation, определяющий изменение интенсивности света при из­ менении расстояния. Диапазон освещения range — это максимальное рас­ стояние, в пределах которого распространяется свет. В данном случае установленный диапазон значительно превышает наши потребности. В программном пакете DirectX SDK (включенном в CD диск) содержится дополнительная информация о математических нюансах проблем осве­ щения.

Итак, мы применили свет к нашему устройству и включили его. Если посмотреть на параметры освещения, можно обратить внимание, что один из них является логической переменной и называется Deferred (Задер­ жанный). По умолчанию его значением является «false», и поэтому мы использовали параметр Comit, чтобы запустить эту опцию. Установка указанного параметра в значение «true» позволяет не вызывать оператор Comit. Необходимо всегда проверять, что ваше освещение установлено и включено, это позволит предвидеть результаты при выполнении прило­ жения.

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

Нормаль представляет собой вектор, перпендикулярный передней сто­ роне объекта, рис. 1.5.

Глава 1. Введение в Direct3D Вершина 2-\ \ Вектор нормали Вершина 1 • Вершина 1 '•Передняя сторона объек -Передняя сторона объекта Рис. 1.5. Нормаль к плоскости вершин Зная это, добавим нормаль к нашему треугольнику. Замените код со­ здания треугольника на следующий:

CustomVertex.PositionNormalColored[] verts=new CustomVertex.PositionNormalColored[3];

verts[0].SetPosition(new Vector3(0.0f, l.Of, l.Of));

verts[0].SetNormal(new Vector3(O.Of, O.Of, -l.Of));

verts[0].Color = System.Drawing.Color.White.ToArgbf);

verts [l].SetPosition(new Vector3(-1.0f, -l.Of, l.Of));

verts [1]. Set-Normal (new Vector3(0.0f, O.Of, -l.Of));

verts[1].Color = System.Drawing.Color. White.ToArgb();

verts[2].SetPosition(new Vector3(1.0f, -l.Of, l.Of));

verts[2].SetNormal(new Vector3(0.0f, O.Of, -l.Of));

verts[2].Color = System.Drawing.Color. White.ToArgb();

Кроме того, нам также следует изменить сам формат вершин, чтобы согласовать наши новые данные:

device.VertexFormat = CustomVertex.PositionNormalColored.Format;

Единственным значительным изменением между этим набором дан­ ных и предыдущим является добавление нормали и того факта, что цвет каждой вершины определяется как белый. Итак, мы определили вектор нормали для каждой вершины, который направлен перпендикулярно пе­ редней поверхности. Поскольку положение наших вершин относитель­ но оси Z не изменилось (l.Of), при изменении координат в плоскости X и Y, перпендикулярный вектор попал бы в область отрицательных значе­ ний оси Z. Запуская теперь наше приложение, можно увидеть, что треу 44 Часть I. Введение в компьютерную графику гольник перевернут и освещен. Попробуйте изменить цвет диффузного компонента, чтобы увидеть, как освещение воздействует на сцену. Обра­ тите внимание на то, что, если вы устанавливаете красный цвет диффуз­ ного компонента, треугольник окажется освещенным красным светом.

Поэксперементируйте со значением этого компонента, чтобы получить различные цветовые варианты.

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

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

состояние отображения (оно же состояние рендера), состояние сэмплера и состояние текстуры. Последние два состояния, использующиеся для текстурирования, мы рассмотрим достаточно скоро. Класс состояния рен­ дера может изменяться в течение того времени, пока Direct3D произво­ дит растеризацию сцены. Существует много различных изменяемых па­ раметров и методов этого класса, включая освещение и устранение неви­ димых поверхностей, которые мы уже использовали в нашем приложе­ нии. Другими опциями, которые вы можете устанавливать в пределах состояния рендера, являются fill mode (покадровый режим с сеткой) и различные параметры режима тумана или дымки. Мы обсудим большин­ ство этих опций и процедур в последующих главах.

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

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

Глава 1. Введение в Direct3D Свопинг-связка представляет собой ряд буферов, управляющих рен­ дерингом. Есть вторичный буфер, определяющий, где выполнить рисо­ вание в пределах этой последовательности. Когда свопинг-связка, создан­ ная с помощью метода SwapEffect.Flip готова, данные вторичных буфе­ ров передаются на первичный буфер, с которого будет считывать данные ваша графическая плата. В то же самое время третичный буфер стано­ вится новым вторичным буфером, а предыдущий первичный буфер «пе­ ремещается» в неиспользованный третичный буфер, рис.1.6.

Операция перемещения flip изменяет местоположение данных, счи­ тываемых видеокартой, и перекачивает старые данные назад в текущее положение вторичного буфера. Для DirectX 9 этот термин используется в общем смысле для сообщения, что вторичный буфер модифицируется как дисплей. В оконном режиме операции перемещения представляют собой копирование данных, подразумевая, что наше устройство не уп­ равляет всем дисплеем, а только его частью. В конечном счете, результат остается тем же самым. В полноэкранном режиме, используя Swap­ Effect.Flip осуществляется фактическое перемещение. Некоторые драй­ веры также осуществляют операции SwapEffect.Discard или Swap Effect.Copy перемещения в полноэкранном режиме.

первичный вторичный третичный буфер буфер буфер до перемещения С Рис. 1.6. Связи вторичных буферов во время операций перемещения Если вы создали свопинг-связку, используя SwapEffect.Copy или SwapEffect.Flip, это гарантирует, что любое существующее обращение не будет воздействовать на вторичный буфер свопинг-связки. Приложе­ ние runtime воспримет это, создавая при необходимости дополнитель­ ные скрытые буферы. Чтобы избежать этого, рекомендуется использо­ вать операцию SwapEffect.Discard. Этот режим позволяет драйверу оп­ ределять наиболее эффективный способ представления вторичного бу 46 Часть I. Введение в компьютерную графику фера. Стоит отметить, что при использовании SwapEffectDiscard необ­ ходимо убедиться в полной очистке вторичного буфера перед запуском новых операций рисования. Во время отладки приложение runtime за­ полнит вторичный буфер случайными данными, если предварительно не будет вызвана функция очистки.

ИСПОЛЬЗОВАНИЕ МУЛЬТИВЫБОРОК (МУЛЬТИ-СЭМПЛИНГА) В ВАШЕМ УСТРОЙСТВЕ Если вы планируете использовать при рисовании сглаживание по-, верхностей (или мульти-сэмплинг), вам необходимо использовать операцию SwapEffect.Discard. Попытка использовать любую другую операцию будет неудачной.

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

Краткие выводы Нарисовав треугольник в нашей сцене, а затем вызвав соответствую­ щую процедуру, мы определили и задали вращение треугольнику. При этом, мы выполнили следующие действия.

Создали Direct3D устройство, прикрепленное к нашей винформе.

• Нарисовали отдельный цветной треугольник в нашем окне.

Задали вращение треугольнику.

Осуществили простое освещение нашей сцены.

Результат выполнения описанных процедур представлен на рис.1.7.

Глава 1. Введение в Direct3D Рис. 1.7. Вращающийся трехмерный треугольник В следующей главе мы рассмотрим различные опции, которые наше устройство может поддерживать, а также создание других устройств.

48 Часть I. Введение в компьютерную графику Глава 2. Выбор подходящего устройства Число возможных перестановок при создании того или иного устрой­ ства достаточно велико. Имея сегодня на рынке обширный выбор графи­ ческих плат, знание всех их параметров практически невозможно. Необ­ ходимо запросить информацию непосредственно в устройстве, чтобы выяснить его характеристики.

В рамках этой главы, мы обсудим следующее.

Поиск всех адаптеров в системе.

• Перечисление форматов, поддерживаемых для каждого устрой­ ства.

Возможности и свойства рассматриваемых устройств.

Адаптеры системы На сегодняшний момент существует множество систем, способных поддерживать работу современных мониторов. Тем не менее, не так много совершенных и эффективных графических карт, способных поддержи­ вать мультимониторный режим «mainstream», использование которых позволяет более широко реализовать возможности отображения графи­ ческой и видео информации. Самые последние платы ATI, nVidia, и Matrox имеют сдвоенные выходы, эти карты позволяют поддерживать работу нескольких мониторов.

Устройства, созданные в приложении Direct3D, завязаны на исполь­ зуемый адаптер. В данном случае мы можем подразумевать адаптер как отдельное аппаратное устройство компьютерной графики, соединяемое с монитором. Современная карта ATI Radeon 9700 является отдельным физическим адаптером, имеет два выхода для монитора (DVI и VGA), и таким образом, распознается приложением в Direct3D как два адаптера.

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

В сборках Direct3D существует статический класс, именуемый класс «Manager», который можно использовать для идентификации адаптеров и имеющихся в системе устройств. Самая первая характеристика в клас­ се Manager — список адаптеров в системе. Этот параметр может рабо­ тать по-разному. Например, он сохраняет значение счетчика «count», со­ общающего о количестве адаптеров в системе. Каждый адаптер может быть индексирован непосредственно (например, Manager.Adapters[0]), или это может быть сквозная нумерация адаптеров в вашей системе.

Чтобы продемонстрировать эти характеристики, напишем простое приложение, которое отобразит на экране разветвленную схему, включа­ ющую список текущих адаптеров в вашей системе и поддерживаемых Глава 2. Выбор подходящего устройства ими режимов отображения. Загрузите Visual Studio, и выполните следу­ ющее:

1. Создайте новый проект Windows Form Project для С#, назвав его как угодно, например, для типового кода — «Enumeration» — «Перечис­ ление».

2. Добавьте ссылку на сборки Microsoft.DirectX.Direct3D и Micro­ soft.DirectX, а также директивы «using» для этих сборок.

3. В представлении проекта для созданной формы windows, добавьте к вашему приложению управление TreeView, находящееся в панели ин­ струментов.

4. Выберите управление TreeView на вашей форме, и нажмите кла­ вишу F4 (или щелкните правой кнопкой мыши, и выберите пункт Properties — Свойства). В свойствах TreeView установите параметр Dock в значение «Fill». В этом случае окно будет всегда заполняться полнос­ тью, даже если оно было изменено.

Теперь необходимо добавить функцию, которая просканирует все адап­ теры в системе и выдаст некоторую информацию относительно поддер­ живаемых режимов каждого из них. Добавьте следующие строки где нибудь в вашем классе:

/

/ We will fill our tree view here / public void LoadGraphics() foreach(Adapter-Information ai in Manager.Adapters) TreeNode root = new TreeNode(ai.Information.Description);

TreeNode driverlnfo = new TreeNode(string.Format ("Driver information: {0} - {1}", ai.Information.DriverName, ai.Information.DriverVersion) );

// Add the driver information to the root node root.Nodes.Add(driverlnfo) ;

// Get each display mode supported TreeNode displayMode = new TreeNode(string.Format ("Current Display Mode: (0}x{i}x{2}\ ai.CurrentDisplayMode.Width, ai.CurrentDisplayMode.Height, ai.CurrentDispIayMode.Format));

foreach(DisplayMode dm in ai.SupportedDisplayModes) TreeNode supportedNode = new TreeNode(string.Format ("Supported: (0} x (1 }x {2}",.

dm.Width, dm.Height, dm.Format));

50 Часть I. Введение в компьютерную графику displayMode.Nodes.Add(supportedNode);

} // Add the display modes root.Nodes.Add(displayMode);

// Add the root node treeViewl.Nodes.Add(root);

Управлять выполнением этого алгоритма довольно просто. Чтобы уз­ нать, что происходит, процедуру выполнения можно всегда прервать.

Сначала идет поиск всех адаптеров в системе. Механизм «Foreach», ис­ пользуемый в С#, делает этот программный цикл на удивление простым.

Для каждого адаптера в системе цикл выполнится один раз и заполнит структуру Adapterlnformation для данного адаптера.

Структура Adapterlnformation имеет несколько значений:

public struct Adapterlnformation ( int Adapter;

DisplayMode CurrentDisplayMode;

AdapterDetails Information;

AdapterDetails GetWhqllnformationO;

DisplayModeEnumerator SupportedDisplayModes;

} Значение Adapter относится к номеру адаптеру, который вы использу­ ете при создании устройства. Порядковые номера адаптера отсчитыва ются от нулевого индекса с номером ordinals, равным числу адаптеров в вашей системе. Два значения параметра AdapterDetails возвращают иден­ тичные результаты, за одним лишь исключением. В отличие от GetWhqlrnformation, в функции Information не возвращается подробная информация о WHQL (Лаборатории по сертификации аппаратных средств для работы в среде Windows). Извлечение этой информации может быть весьма долгим, поэтому целесообразно отделить данную операцию.

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

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

Значение CurrentDisplayMode возвращает информацию о режиме визу­ ального отображения в используемом адаптере, в то время как Supported DisplayModes возвращает список всех режимов визуального отображе­ ния, поддерживаемых данным адаптером.

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

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

using (Forml frm = new Forml()) { frm.LoadGraphics lb Application.Run(frm) ;

} При выполнении данного приложения на экране появится окно, соот­ ветствующее рис.2.1.

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

Перечисляемые форматы описываются с помощью буквенно-цифрового кода. Символ представляет тип данных;

номер — число битов поддержи­ ваемых данных. Некоторые из символов могут быть следующие:

А — alpha, альфа X — unused, неиспользуемый R — red, красный G — green, зеленый В — blue, синий L — luminance, яркость Р — palette, палитра Часть I. Введение в компьютерную графику Рис. 2.1. Разветвленная схема режимов адаптера Полная сумма всех битов в формате определяет полный размер фор­ мата. Например, формат X8R8G8B8, рис.2.1, означает 32-разрядный фор­ мат с 8 битами, используемыми для каждого цвета (красный, зеленый и синий) и с неиспользованными 8 битами.

ФОРМАТЫ ДЛЯ ВТОРИЧНЫХ БУФЕРОВ И ДИСПЛЕЕВ В большом списке форматов, есть несколько подходящих для ис­ пользования в качестве вторичного буфера или формата дисплея.

Некоторые из них:

A2R10G10B10 A1R5G5B A8R8G8B8 X1R5G5B X8R8G8B8 R5G6B Форматы дисплея могут быть теми же, что и форматы вторичных буферов, за исключением тех, которые содержат символьный ком­ понент. Единственный формат, который может использоваться с символьной компонентой для дисплея, —- это формат A2R10G10B10, но только в полноэкранный режиме.

Обратите внимание, что, поскольку эти форматы поддерживаются приложением Direct3D в качестве форматов вторичных буферов, не­ посредственно адаптер может не поддерживать их. Для нашего при­ мера, единственными поддерживаемыми форматами, которые мы получили, были X8R8G8B8 и R5G6B5.

Глава 2. Выбор подходящего устройства Итак, мы собрали достаточно информации, чтобы определить поряд­ ковый адаптер для устройства, которое хотим создать, а также вторич­ ный буферный формат, который мы хотим поддерживать.

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

Используйте следующую функцию:

public static System.Boolean CheckDeviceType ( System.Int32 adapter, Microsoft.DirectX.Direct3D.DeviceType checkType, Microsoft.DirectX.Direct3D.Format displayFormat, Microsoft.DirectX.Direct3D.Format baciBufferformat, System.Boolean windowed, System.Int32 result ) Данная функция позволяет быстро определить, поддерживает ли ваше устройство используемый тип формата. Первый параметр — adapter, те­ стируемый адаптер. Второй — checktype, тип проверяемого устройства (как правило, это DeviceType.Hardware). Окончательно определяются ото­ бражаемый формат displayFormat и формат вторичного буфера Ьаск BufferFormat, а затем, режим работы (оконный (windowed) или полноэк­ ранный). Последний параметр — дополнительный, при использовании он возвратит целочисленный код (HRESULT в СОМ) функции. Данный алгоритм возвращает значение «true», если данный тип устройства под­ держивается, в противном случае возвращает значение «false».

ПРЕОБРАЗОВАНИЕ ОПРЕДЕЛЯЕМОГО ФОРМАТА Важно обратить внимание, что в оконном режиме формат вторич­ ного буфера не должен соответствовать формату режима визуаль­ ного отображения, если аппаратные средства могут поддерживать соответствующее цветовое преобразование. Метод CheckDevi­ ceType возвращает соответствующие результаты, независимо от того, доступен данный формат или нет, также для определения это­ го напрямую возможно использование метода CheckDeviceFormat Conversion класса Manager. В оконном режиме вы можете исполь­ зовать формат Format.Unknown.

54 Часть I. Введение в компьютерную графику Было бы весьма полезно знать заранее те типы форматов, которые вы будете использовать. Это освобождает от необходимости перебирать все возможные форматы и устройства.

Проверка возможностей устройства Различные возможности аппаратных средств описываются термином «capability» — возможность. В приложении Direct3D runtime имеется оп­ ция, которая перечисляет все реализуемые возможности используемого устройства. Как только устройство создано, вы можете использовать оп­ цию устройства capability для определения поддерживаемых возможно­ стей. Если устройство еще не создано, вы так же можете узнать возмож­ ные поддерживаемые характеристики этого устройства с помощью оп­ ределенного метода класса Manager.

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

Однако, мы не можем добавить список возможностей к имеющейся раз­ ветвленной схеме драйверов и параметров из-за их большого числа. Са­ мый простой способ показать эти данные состоит в использовании тек­ стового поля.

Теперь вернемся в процедуру view нашей формы и попробуем пере­ ключать параметры разветвленной схемы представления от позиции «Fill» до позиции «Left». Уменьшите размер окна вполовину. Теперь добавьте текстовое поле к окну, установите параметр Dock в положение «Fill».

Также убедитесь, что строка «Multiline» установлена в положение «true», и параметр прокрутки «Scrollbars» установлен в положение «Both» для текстового поля.

Далее нам следует добавить обработчик событий к приложению так, чтобы он создавал текстовое поле с параметрами обнаруженного адапте­ ра. Для этого необходимо привязать опцию «AfterSelect» к событию об­ наружения адаптера (предполагается, что вы уже знаете, как отслежи­ вать эти события). Используйте следующие команды:

private void treeViewl_AfterSelect(object sender,System.Windows.Forms.TreeViewEventArgs e) ( if (e.Node.Parent == null) // root node ( // Get the device caps for this node Глава 2. Выбор подходящего устройства textBoxl.Text = е.Node.Text + " Capabilities: \r\n\r\n" + Manager.GetDeviceCaps (e.Node.Index, DeviceType.Hardware).ToString().Replace("\n", "\r\n");

Как видите, это достаточно просто. Для корневых папок, которые ото­ бражают названия адаптеров после того, как они выбраны, мы вызываем функцию Manager.GetDeviceCaps для соответствующего номера адапте­ ра. Параметр ToString этой строки возвращает чрезвычайно большой спи­ сок всех возможностей данного устройства. Выполнение данного прило­ жения приведет к появлению на экране более подробной разветвленной схемы, см. пример на рис.2.2.

ИСПОЛЬЗОВАНИЕ ОПЦИИ «CAPABILITY» ДЛЯ НЕПРОГРАММИРУЕМОГО КОНВЕЙЕРА Многие из возможностей, перечисленных в структуре «capability» связаны непосредственно с непрограммируемым конвейером (на­ пример, значение MaxActiveLights). Если вы используете програм­ мируемый конвейер, многие из возможностей не будут использо­ ваны в вашей сцене. Позднее мы обсудим различия между не­ программируемым и программируемым конвейерами.

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

Структура «Capability» разбита главным образом на две группы. Первая группа — логические значения, которые определяют, поддерживается ли данная аппаратная возможность или нет. Например, SupportsAlphaCom раге принимает значение «true», если устройство поддерживает тексто­ вые символы. Другая группа класса «Capability» возвращает «фактичес ские значения, например значение MaxActiveLights, которое определяет максимальное число активных подсветок, реализуемых в сцене. Далее во мере необходимости мы рассмотрим и опишем отдельные возможно­ сти устройства.

56 Часть I. Введение в компьютерную графику РИС. 2.2. Возможности и режимы обнаруженных устройств Краткие выводы В этой главе мы рассмотрели выбор подходящего графического уст­ ройства для работы с нашим приложением. В главе были охвачены сле­ дующие пункты.

Поиск и обнаружение всех имеющихся в системе адаптеров.

• Определение форматов, поддерживаемых нашим устройством.

• Определение и описание возможностей имеющихся устройств.

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

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

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

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

public VertexBuffer ( Microsoft.DirectX.Direct3D.Device device, System.Int sizeOfBufferInByt.es, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.VertexFormats vertexFormat, Microsoft.DirectX.Direct3D.Pool pool ) public VertexBuffer ( System.Type typeVertexType, System.Int32 numVerts, Microsoft.DirectX.Direct3D.Device device, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.VertexFormats vertexFormat, Microsoft.DirectX.Direct3D.Pool pool ) Опишем каждый параметр.

• Device — устройство, которое используется в Direct3D для созда­ ния вершинного буфера. Данный буфер будет поддерживаться только этим устройством.

• SizeOfBufferlnBytes — задаваемый размер вершинного буфера в байтах. Если вы используете в конструкторе этот параметр, буфер способен поддерживать любой тип вершин.

58 Часть I. Введение в компьютерную графику TypeVertexType — задается, если вы хотите, чтобы буфер включал только один тип вершин. Либо это может быть тип одной из встро­ енных структур вершин в классе CustomVertex, либо тип вершин, определяемый пользователем. Данное значение не может быть пу­ стым.

NumVerts — максимальное количество вершин, которые вы хоти­ те сохранить в буфере. Это значение должно быть положитель­ ным и ненулевым.

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

DoNotClip — используется для того, чтобы исключить режим clipping (отсечения) для этого буфера. Используя этот флажок, необходимо при использовании рендеринга из вершинного буфера установить параметр clipping состояния рендера в зна­ чение «false».

Dynamic — используется для сообщения приложению, что вершинный буфер требует использования динамической па­ мяти. Если этот флажок не определен, вершинный буфер яв­ ляется статическим. Статические вершинные буферы обычно хранятся в видеопамяти, в то время как динамические буфе­ ры хранятся в памяти AGP. Выбор данной опции необходим для драйверов, чтобы определить, где сохранить данные. Бо­ лее подробно с режимом Dynamic можно ознакомиться в до­ кументации на DirectX SDK.

• Npatches — сообщает, что данный буфер используется для ри­ сования патчей N-Patches.

Points — сообщает, что данный буфер используется для рисо­ вания точек.

RTPatches — сообщает, что данный буфер используется для рисования примитивов более высокого порядка.

SoftwareProcessing — сообщает, что обработка вершин долж­ на проводиться программным обеспечением, в противном слу­ чае обработка ведется аппаратными средствами.

• WriteOnly — сообщает, что вершинный буфер не будет счи тываться в случае, когда нет необходимости считывать дан­ ные вершинного буфера. Данная опция позволяет разгрузить видеопамять.

• VertexFormat — определяет формат вершин, которые будут сохра­ нены в этом буфере. Вы можете выбирать опцию VertexFormat.No пе, если планируете зарезервировать этот буфер.

• Pool — определяет пул памяти, куда вы хотите поместить вер­ шинный буфер. Вам следует выбрать одно из следующих разме­ щений пула памяти.

Глава 3. Введение в технологию рендеринга • Default — вершинный буфер размещен в памяти, объединяет большинство размещенных в ней данных. Данные располага­ ются либо в видеопамяти, либо в памяти AGP, в зависимости от параметра использования. Вершинные буферы, созданные в этом пуле памяти, переопределяются автоматически перед сбросом устройства.

• SystemMemory — данные вершинного буфера помещаются в системную память, где являются недоступными для устрой­ ства.

• Scratch — системный пул памяти, не связанный с устройством и не используемый устройством. Удобен при управлении дан­ ными, при этом не привязан к специфическому формату уст­ ройства.

СОЗДАНИЕ ВЕРШИННЫХ БУФЕРОВ С ПОМОЩЬЮ НЕУПРАВЛЯЕМЫХ СОМ УКАЗАТЕЛЕЙ Как и в самом устройстве, в приложении имеется перегрузка для вершинного буфера, которая принимает указатель IntPtr. Это зна­ чение используется для передачи указателя интерфейса СОМ в не­ управляемый интерфейс IDirect3DvertexBuffer9. Это удобно, когда вы хотите использовать вершинный буфер, созданный с помощью внешнего (неуправляемого) источника. Любое другое управляемое приложение никогда не будет использовать данный конструктор, и это значение не может быть пустым или нулевым.

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

private Device device = null;

private VertexBuffer vb = null;

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

// Create our device device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);

60 Часть I. Введение в компьютерную графику CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[3];

verts[0].SetPosition(new Vector3(0.Of, I.Of, l.Of));

verts [0].Color = System.Drawing.Color.Aqua.ToArgb();

verts[l].SetPosition(new Vector3(-1.0f, -l.Of, l.Of));

verts [1].Color = System.Drawing.Color.Black.ToArgb() verts[2].SetPosition(new Vector3(l.Of, -l.Of, l.Of));

verts [2].Color - System.Drawing.Color.Purple.ToArgbO ;

vb = new VertexBuffer(typeof(CustomVertex.PositionCoiored), 3, device, Usage.Dynamic :

Usage.WriteOnly, CustomVertex.PositionCoiored.Format, Pool.Default);

vb.SetData(verts, 0, LockFlags.None);

Единственные изменения, появившиеся здесь, — две новых строки после кода создания треугольника. Мы сначала создаем вершинный бу­ фер для записи трех значений вершинной структуры, которые мы уже объявили. Мы хотим, чтобы буфер был настроен только на запись, был динамическим и сохранялся только в заданном по умолчанию пуле памя­ ти для увеличения скорости выполнения. Далее необходимо поместить наш список вершин треугольника в вершинный буфер, используя метод SetData. Этот метод принимает любой общий объект (подобно методу DrawUserPrimitives) в качестве первого значения «verts». Второе значе­ ние «offset» — величина смещения размещаемых данных. Поскольку мы хотим записать все данные, принимаем здесь смещение, равное нулю.

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

При компилировании данного приложения система выдаст сообще­ ние об ошибке компиляции, поскольку функция DrawUserPrimitives при обращении к OnPaint затребует переменную «verts». Необходимо сооб­ щить Direct3D, что мы хотим рисовать объект именно из нашего вершин­ ного буфера, а не из массива, который мы объявили прежде.

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

public void SetStreamSource ( System.Int32 streamNumber, Microsoft.DirectX.Direct3D.VertexBuffer streamData, System.Int32 offsetlnBytes, System.Int32 stride ) public void SetStreamSource ( System.Int32 streamNumber, Microsoft.DirectX.Direct3D.VertexBuffer streamData, System.Int32 offsetlnBytes ) Глава 3. Введение в технологию рендеринга Единственным различием между этими двумя перегрузками является то, что каждая содержит дополнительное значение для размера шага по­ тока. Первый параметр streamNumber — номер потока, который мы бу­ дем использовать для этих данных. В основном мы будем использовать нулевое значение, однако, позже мы обсудим и множественные потоки.

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

Замените обращение к методу рисования drawing следующими стро­ ками:

device.SetStreamSource(0, vb, 0);

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

Как уже отмечалось, мы записываем нулевое значение номера потока данных вершинного буфера, и поскольку хотим использовать все дан­ ные, выбираем нулевое значение смещения. Обратите внимание, что мы также изменили фактический вызов создания рисунка. Поскольку мы имеем все наши данные в вершинном буфере, нет необходимости вызы­ вать метод DrawUserPrimitives (данная функция предназначена для рисо­ вания объектов, данные о которых непосредственно определяются пользо­ вателем). Более универсальная функция DrawPrimitives рисует примити­ вы из нашего потокового источника. Метод DrawPrimitives имеет три параметра, первый — тип примитива, который мы уже обсудили. Второй параметр — начальная вершина в потоке. Последний параметр — число примитивов, которые мы будем рисовать.

Даже эта простая демонстрация отображения треугольника с исполь­ зованием вершинного буфера показывает увеличение быстродействия приблизительно на 10%, в основном за счет скорости передачи кадров.

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

Есть несколько причин, вызывающих такое поведение, о двух из них уже кратко упоминалось. В предыдущей главе было сказано, что при из­ менении размеров окна устройство автоматически сбрасывалось. Это означает, что когда после сброса в пуле памяти (например, вершинном буфере) создается ресурс, этот пул устанавливается автоматически при сбрасывании устройства. Таким образом, во время изменения окна уст 62 Часть I. Введение в компьютерную графику ройство сбрасывается, и устанавливается вершинный буфер. Одной из изящных особенностей Управляемого DirectX является то, что он авто­ матически воссоздает вершинный буфер после того, как устройство сбра­ сывается, при этом в буфере не будет никаких данных, и, следовательно, ничего не отобразится на экране.

Вершинный буфер имеет обработчик события «created», который от­ слеживает момент создания буфера и готовности его к заполнению. Для использования этого обработчика событий необходимо переписать наше приложение, добавив следующий код:

private void OnVertexBufferCreate(object sender, EventArgs e) ( VertexBuffer buffer = (VertexBuffer)sender;

CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[3];

verts[0].SetPosition(new Vector3(O.Of, l.Of, l.Of ));

verts[0].Color = System.Drawing.Color.Aqua.ToArgb();

verts[l].SetPosition(new Vector3(-1.0f, -l.Of, l.Of));

verts[1].Color = System. Drawing.Color.Black.ToArgb();

verts[2].SetPosition(new Vector3(1.0f, -l.Of, l.Of));

verts[2].Color = System.Drawing.Color.Purple.ToArgb();

buffer.SetData(verts, 0, LockFlags.None);

Эта функция имеет стандартное описание обработчика событий, вклю­ чая регистрацию события и список параметров. В нашем случае список параметров не используется, так что это значение можно проигнориро­ вать. Таким образом, вначале создается вершинный буфер, затем отсле­ живается данное событие, и значение сендера передается назад в вер­ шинный буфер. Далее мы следуем тем же путем, что и в последний раз, когда создавали наши данные и вызывали метод SetData. Мы должны переписать код создания треугольника в методе инициализации InitializeGraphics, используя вместо него следующий две строки:

vb.Created += new EventHandler(this.OnVertexBufferCreate);

OnVertexBufferCreate(vb, null);

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

Глава 3. Введение в технологию рендеринга ВРЕМЯ РАБОТЫ РЕСУРСА Все графические ресурсы будут размещаться автоматически, если в течение сброса устройства они сохранены в видеопамяти;

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

Итак, используя вершинные буферы и видеопамять, мы успешно усо­ вершенствовали наше приложение для отображения трехмерного треу­ гольника. Теперь, используя дополнительные рендеры, можно попробо­ вать сделать процесс рисования более захватывающим. Методы и код станут более сложными, но основные принципы сохранятся.

Как вы знаете, вся геометрия в трехмерной сцене составлена из поли­ гонов, в частности из треугольников, даже если вы отображаете куб, по­ скольку квадрат или прямоугольник можно сложить из двух треугольни­ ков, а куб можно создать из шести квадратов. Единственные координаты, которые необходимы для создания куба, — это восемь угловых вершин куба. Давайте изменим наш алгоритм создания геометрии, см. листинг 3.1:

Листинг 3.1. Создание куба или параллелепипеда.

CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[36];

// Front face verts[0] = new CustomVertex.PositionColored(-1.0f, l.Of, l.Of, Color.Red.ToArgb()) ;

verts[l] = new CustomVertex.PositionColored(-1.0f, -l.Of, l.Of, Color.Red.ToArgb());

verts[2] = new CustomVertex.PositionColored(1.0f, l.Of, l.Of, Color.Red.ToArgb());

verts[3] = new CustomVertex.PositionColored(-1.0f, -l.Of, l.Of, Color.Red.ToArgb());

verts[4] = new CustomVertex.PositionColored(1.0f, -l.Of, l.Of, Color.Red.ToArgb());

verts[5] = new CustomVertex.PositionColored(l.Of, l.Of, l.Of, Color.Red.ToArgb());

// Back face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[6] = new CustomVertex.PositionColored(-1.0f, l.Of, -l.Of, Color. Blue.ToArgb());

verts[7] = new CustomVertex.PositionColored(l.Of, l.Of, -l.Of, Color.Blue.ToArgb());

64 Часть I. Введение в компьютерную графику verts[8] = new CustomVertex.PositionColored(-1.0f, -l.Of, -l.Of, Color.Blue.ToArgb());

verts[9J = new CustomVertex.PositionCoiored(-1.0f, -l.Of, -l.Of, Color. Blue. ToArgb());

verts[lO] = new CustomVertex.PositionCoiored(1.0f, l.Of, -l.Of, Color.Blue.ToArgb(l);

verts[ll] = new CustomVertex.PositionColored(1,Of, -l.Of, -l.Of, Color. Blue. ToArgb());

// Top face verts[12] = new CustomVertex.PositionColored(-1.0f, l.Of, l.Of, Color.Yellow.ToArgb());

verts[13] = new CustomVertex.PositionColored(1.0f, l.Of, -l.Of, Color.Yellow.ToArgb()) ;

verts[14! = new CustomVertex.PositionColored(-1.0f, l.Of, -l.Of, Color.Yellow.ToArgb());

verts[15J = new CustomVertex.PositionColored(-1.0f, l.Of, l.Of, Color.Yellow.ToArgb());

verts[16] = new CustomVertex.PositionColored(1.0f, l.Of, l.Of, Color.Yellow.ToArgb());

verts[17] = new CustomVertex.PositionCoiored(1.0f, l.Of, -l.Of, Color. Yellow. ToArgb());

// Bottom face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[18] = new CustomVertex.PositionColored(-1.0f, -l.Of, l.Of, Color.Black.ToArgb());

verts[19] = new CustomVertex.PositionColored(-1.0f, -l.Of, -l.Of, Color.Black.ToArgb());

verts[20] = new CustomVertex.PositionColored(l.Of, -l.Of, -l.Of, Color.Black.ToArgb());

verts[21j = new CustomVertex.PositionColored(-1.0f, -l.Of, l.Of, Color.Black.ToArgb());

verts[22] = new CustomVertex.PositionColored(1.0f, -l.Of, -l.Of, Color. Black.ToArgb());

verts[23] = new CustomVertex.PositionCoiored(1.0f, -l.Of, l.Of, Color.Black.ToArgb());

// Left face verts[24] = new CustomVertex.PositionColored(-1.0f, l.Of, l.Of, Color.Gray.ToArgb());

verts[25] = new CustomVertex.PositionColored(-1.0f, -l.Of, -l.Of, Color.Gray.ToArgb());

verts[26] = new CustomVertex.PositionColored(-1.0f, -l.Of, l.Of, Color. Gray. ToArgb());

verts[27] = new CustomVertex.PositionColored(-1.0f, l.Of, -l.Of, Color.Gray.ToArgb());

verts[28] = new CustomVertex.PositionColored(-1.0f, -l.Of, -l.Of, Color.Gray.ToArgb());

Глава 3. Введение в технологию рендеринга verts[29] = new CustomVertex.PositionColored(-1.0f, l.Of, 1.Of, Color.Gray.ToArgbO);

// Right face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[30] - new CustomVertex.PositionColored(1.0f, l.Of, l.Of, Color.Green.ToArgbO ) ;

verts[31] = new CustomVertex.PositionCoiored(1.0f, -l.Of, l.Of, Color.Green.ToArgbO );

verts[32] = new CustomVertex.PositionColored(1.0f, -l.Of, -l.Of, Color.Green.ToArgbO);

verts[33] = new CustomVertex.PositionColored(1.0f, l.Of, -l.Of, Color.Green.ToArgbf)) ;

verts[34] = new CustomVertex.PositionColoredfl.Of, l.Of, l.Of, Color.Green.ToArgbO ) ;

verts[35] = new CustomVertex.PositionColored(1.0f, -l.Of, -l.Of, Color.Green.ToArgbO ) ;

buffer.SetData(verts, 0, LockFlags.None);

В данном методе мы формируем необходимое количество вершин для создания нашего объекта, в данном случае 36 вершин. При этом вы мо­ жете использовать исходник программы, имеющейся на CD диске. Как уже упоминалось, кубический объект будет создан из 12 треугольников, каждый треугольник имеет 3 вершины. Выполнение данного кода анало­ гично процедуре обращения к функции SetData.

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

vb = new VertexBuffer(typeof(CustomVertex.PositionColored), 36, device, Usage.Dynamic ! Osage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);

device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI, angle / (float)Math.PI * 2.Of, angle / (float)Math.PI);

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);

Главное, что мы здесь делаем, — это изменение размера вершинного буфера, который мы создали для отображения всех необходимых для рен­ деринга данных. Мы также пытаемся изменить и характер вращения, чтобы получить более эффектную картинку. Наконец, мы пытаемся ото­ бразить 12 примитивов более эффективно, чем мы отображали наш один единственный треугольник. Действительно, поскольку наш новый куби­ ческий объект сформирован полностью как ЗD-объект и является полно­ стью заполненным и описанным, у нас больше нет необходимости моде З.!к 66 Часть I. Введение в компьютерную графику пировать и отображать обратные невидимые стороны треугольников. Мы можем использовать заданный по умолчанию в Direct3D режим отбора (против часовой стрелки). Поэтому необходимо убрать строку, описыва­ ющую режим отбора «cullig», из нашего источника. Затем попробуем по­ вторно выполнить приложение.

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

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

Будем рисовать фигуру из трех кубических объектов, некоторые гра­ ни которых соприкасаются. Поскольку наши текущие параметры каме­ ры настроены на наш первый куб, для охвата всей сцены мы переместим камеру немного назад. Измените параметр Look в функции следующим образом:

device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 18.Of), new Vector3(), new Vector3(0,1,0));

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

Добавьте следующие строки программы после обращения к Draw Primitives:

device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI, angle / (float)Math.PI / 2.Of, angle / (float)Math.PI * 4.Of) * Matrix.Translation(5.Of, O.Of, O.Of);

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);

device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI, angle / (float)Math.PI * 4.Of, angle / (float)Math.PI / 2.Of) * Matrix.Translation(-5.Of, O.Of, O.Of);

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);

Приложение Direct3D уже «знает», какой тип вершин мы планируем рисовать, поскольку с помощью параметра VertexFormat мы определили для рисования наш первый куб. Благодаря функции SetStreamSource (пока только для первого куба) приложению Direct3D также «известно», из ка Глава 3. Введение в технологию рендеринга изго вершинного буфера извлекаются данные. Что же необходимо знать приложению Direct3D, чтобы начать рисовать второй и третий куб? Един­ ственная информация, которая необходима, это — где и что рисовать.

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

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

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

В составе CD-диска включен исходник программы, которая позволяет рисовать девять кубических объектов быстрее, чем три, которые описа­ ны здесь, рис.3.1.

Рис. 3.1. Раскрашенные кубы 68 Часть I. Введение в компьютерную графику Текстурирование объектов Несмотря на использование рендеринга с цветами и подсветкой, объек­ ты все равно выглядят не так реалистично, как хотелось бы. Термин «тек­ стурирование» («texture») при описании нетрехмерных приложений обыч­ но описывает шероховатость или неровность рисуемого объекта. Тексту­ ры в трехмерной сцене — это, по существу, плоские двухмерные bitmap рисунки, которые могут использоваться для моделирования текстуры на примитиве. Например, вы могли бы взять точечный рисунок травы, что­ бы отобразить реалистичный холм, или, возможно, облака, чтобы ото­ бразить небо. Приложение Direct3D может отображать до восьми тек­ стур одновременно для каждого примитива, но пока давайте разберем одну текстуру на одном примитиве.

Рис. 3.2. Визуализация текстурных координат В то время как Direct3D использует структуру bitmap в качестве фор­ мата текстуры, любой точечный рисунок, который вы загружаете, может быть использован для текстурирования объекта. Каким же образом мож­ но преобразовать плоский 2D точечный рисунок в нечто такое, что мо­ жет нарисовать трехмерный объект? Каждый объект, представляемый в сцене, запрашивает координаты текстуры, которые используются для ото­ бражения каждого тексела (элемента текстуры) в соответствующем пик­ селе на экране в течение процедуры растеризации. Тексел, от английско­ го слова texel, — сокращенное название элемента текстуры, или соответ­ ствующее значение цвета для каждого адреса в текстуре. Под адресом Глава 3. Введение в технологию рендеринга можно подразумевать номер строки и столбца, называемые «U» и «V», соответственно. Обычно это скалярные значения, диапазон которых со­ ставляет от 0,0 до 1,0. Значение 0,0 соответствует верхнему левому углу расположения текстуры, тогда как 1,1 соответствует правому нижнему углу расположения текстуры. Центр текстуры соответствует значению 0,5, 0,5, рис.3.2.

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

Мы заменим компонент «color» (цвет) данных наших вершин данными координат текстуры. Для того чтобы иметь наш объект и цветным, и тек­ стурным, мы просто будем использовать текстуру для определения цвета каждого примитива. Перепишите код создания вершины, как показано в листинге 3.2:

Листинг 3.2. Данные текстур куба.

CustomV'ertex.PositionTextured[] verts = new CustomV'ertex.PositionTextured [36];

// Front face verts[0] = new CustomVertex.PositionTextured(-1.0f, l.Of, l.Of, O.Of, O.Of);

verts[1] = new CustomVertex.PositionTextured(-1.0f, -l.Of, l.Of, O.Of, l.Of);

verts[2] = new CustomVertex.PositionTextured(1.0f, l.Of, l.Of, l.Of, O.Of);

verts[3] = new CustomVertex.PositionTextured(-l.Of, -l.Of, l.Of, O.Of, l.Of);

verts[4] = new CustomVertex.PositionTextured(1.0f, -l.Of, l.Of, l.Of, l.Of);

verts[5] = new CustomVertex.PositionTextured(1.0f, l.Of, l.Of, l.Of, O.Of);

// Back face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[6] = new CastomVertex.PositionTextured(-1.0f, l.Of, -l.Of, O.Of, O.Of);

verts[7] = new CustomVertex.PositionTextured(i.Of, l.Of, -l.Of, l.Of, O.Of);

verts[8] = new CustomVertex.PositionTextured(-1.0f, -l.Of, -l.Of, O.Of, l.Of);

verts[9j - new CustomVertex.PositionTextured(-1.0f, -l.Of, -l.Of, O.Of, l.Of);

verts[10] = new CustomVertex.PositionTextured(l.Of, l.Of, -l.Of, l.Of, O.Of);

verts[11] = new CustomVertex.PositionTextured(l.Of, -l.Of, -l.Of, l.Of, l.Of);

70 Часть I. Введение в компьютерную графику // Top face verts[12] = new CustomVertex.PositionTextured(-1.0f, l.Of, l.Of, O.Of, O.Of);

verts[13] = new CustomVertex.PositionTextured(l.Of, l.Of, -l.Of, l.Of, l.Of);

verts[14] = new CustomVertex.PositionTextured(-1.0f, l.Of, -l.Of, O.Of, l.Of);

verts[15] = new CustomVertex.PositionTextured(-1.0f, l.Of, l.Of, O.Of, O.Of);

verts[16] = new CustomVertex.PositionTextured(1.0f, l.Of, l.Of, l.Of, O.Of);

verts[17] = new CustomVertex.PositionTextured(1.0f, l.Of, -l.Of, l.Of, l.Of);

// Bottom face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[18] = new CustomVertex.PositionTextured(-1.0f, -l.Of, l.Of, O.Of, O.Of);

verts[19] = new CustomVertex.PositionTextured(-1.0f, -l.Of, -l.Of, O.Of, l.Of);

verts[20] = new CustomVertex.PositionTextured(1.0f, -l.Of, -l.Of, l.Of, l.Of);

verts[21] = new CustomVertex.PositionTextured(-1.0f, -l.Of, l.Of, O.Of, O.Of);

verts[22] = new CustomVertex.PositionTextured(1.0f, -l.Of, -l.Of, l.Of, l.Of);

verts[23] = new CustomVertex.PositionTextured(1.0f, -l.Of, l.Of, l.Of, O.Of);

// Left face verts[24] = new CustomVertex.PositionTextured(-1.0f, l.Of, l.Of, O.Of, O.Of);

verts[25] = new CustomVertex.PositionTextured(-1.0f, -l.Of, -l.Of, l.Of, l.Of);

verts[26] = new CustomVertex.PositionTextured(-1.0f, -l.Of, l.Of, l.Of, O.Of);

verts[27] = new CustomVertex.PositionTextured(-1.0f, l.Of, -l.Of, O.Of, l.Of);

verts[28] = new CustomVertex.PositionTextured(-1.0f, -l.Of, -l.Of, l.Of, l.Of);

verts[29] = new CustomVertex.PositionTextured(-1.0f, l.Of, l.Of, O.Of, O.Of);

// Right face (remember this is facing *away* from the camera, so vertices should be clockwise order) verts[30] = new CustomVertex.PositionTextured(1.0f, l.Of, l.Of, O.Of, O.Of);

Глава 3. Введение в технологию рендеринга verts[31] = new CustomVertex.PositionTextured(1.0f, -1.Of, 1.Of, l.Of, O.Of);

verts[32] = new CustomVertex.PositionTextured(1.0f, -l.Of, -l.Of, l.Of, l.Of);

verts[33] = new CustomVertex.PositionTextured(1.0f, l.Of, -l.Of, O.Of, l.Of);

verts[34] = new CustomVertex.PositionTextured(1.0f, l.Of, l.Of, O.Of, O.Of);

verts[35] = new CustomVertex.PositionTextured(1.0f, -l.Of, -l.Of, l.Of, l.Of);

buffer.SetData(verts, 0, LockFlags.None);

Очевидно, что самое большое изменение здесь — тип данных, исполь­ зуемый для сохранения списка вершин. Последние два значения с плава­ ющей точкой, сохраненные в каждой вершине, представляют собой U и V координаты в текстуре, используемой для отображения примитива. По­ скольку каждая грань куба представляет собой квадрат, текстуры будут также квадратными, таким образом, имеет смысл отображать каждый квадрат непосредственно в текстуре. Обратите внимание, что вершина в верхнем левом углу соответствует техселу 0,0, а вершина в правой ниж­ ней части отображает непосредственно тексел 1,1. При отображении каж­ дая сторона квадрата будет включать текстуру целиком. Для того чтобы приложение выполнялось без прерываний, необходимо изменить и за­ дать тип данных вершинного буфера:

vb = new VertexBuffer(typeof(CustomVertex.PositionTextured), 36, device, Usage.Dynamic j Usage.WriteOnly, CustomVertex.PositionTextured.Format, Pool.Default);

Давайте немного упростим процедуру рисования наших кубов, доба­ вив следующий код:

private void DrawBox(float yaw, float pitch, float roll, float x, float y, float z, Texture t) ( angle += O.Olf;

device.Transform.World = Matrix.RotationYawPitchRol(yaw, pitch, roll) * Matrix.Translation(x, y, z);

device.SetTexture(0, t);

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);

Первые шесть параметров этой функции те же самые, что мы исполь­ зовали в начале. Мы приводим отклонение (yaw), шаг (pitch) и наклон 72 Часть I. Введение в компьютерную графику (roll) для нашего вращения куба, плюс X, Y, и Z для перемещения куба.

Последний параметр texture t новый для нас, хотя он представляет тек­ стуру для рендеринга нашего объекта. Мы также вызываем метод SetTexture для устройства, чтобы сообщить приложению Direct3D, ка­ кую текстуру мы хотим использовать при рендеринге этого примитива.

Первый параметр этого метода определяет стадию, на которой мы управ­ ляем текстурой. Если вспомнить, мы уже упоминали о возможности ото­ бражения до восьми текстур для примитива. Таким образом, этот первый параметр является индексом текстур. Поскольку сейчас мы имеем набор координат одной текстуры, мы будем всегда использовать в качестве пер­ вого индекса значение 0. Также обратите внимание, что, поскольку мы изменяем значение «угла» поворота и осуществляем преобразование ко­ ординат, вы можете удалить соответствующие строки из метода SetupCamera.

Прежде чем изменить наш код рендеринга для вызова новой функ­ ции, мы сначала должны определить те текстуры, которые будем исполь­ зовать. Демонстрационная программа, имеющаяся на CD диске, включа­ ет в себя три текстуры — puck.bmp, ground.bmp, и banana.bmp, прило­ женные в качестве ресурсов в исходном проекте. В меню проекта на­ жмите «Add Existing Item» и добавьте три изображения. После этого не­ обходимо рассмотреть свойства каждого и изменить пункт «Build Action» (Компоновка) на «Embedded Resource» (Вложенный Ресурс). Теперь мы должны объявить переменные для наших текстур, добавив их после со­ здания вершинного буфера:

private Texture tex = null;

private Texture texl = null;

private Texture tex2 = null;

Эти текстуры мы будем использовать для рисования нашего объекта.

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

tex = new Texture(device, new Bitmap(this.GetIype(), "puck.bmp"), 0, Pool.Managed);

texl = new Texture(device, new Bitmap(this.GetType(), "banana.bmp"), 0, Pool.Managed);

tex2 = new Texture(device, new Bitmap(this.GetType(), "ground.bmp"), 0, Pool.Managed);

Данный метод для нашей текстуры включает четыре параметра. Пер­ вый — device, устройство, которое мы будем использовать для выполне Глава 3. Введение в технологию рендеринга ния текстуры. Все ресурсы (текстуры, вершинные буферы, и пр.) в сцене будут связаны с устройством. Следующий параметр — System.Dra­ wing.Bitmap — мы будет использовать для получения данных для этой текстуры. В этом случае мы используем конструктор bitmap, чтобы заг­ рузить наш файл из ресурсов. Третий используемый параметр мы уже кратко обсуждали при описании вершинных буферов, в нашем примере мы присвоили ему значение «О» и пока не используем. Последний пара­ метр — пул памяти, используемый для хранения текстуры. Для удобства будем использовать управляемый пул. Ниже приведены другие конструк­ торы или методы, доступные для текстур:

public Texture ( System.IntPtr Ip, Microsoft.DirectX.Direct3D.Device device, Microsoft.DirectX.Direct3D.Pool pool ) public Texture ( Microsoft.DirectX.Direct3D.Device device, System.Int width, System.Int32 height, System.Int32 numLevels, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.Format format, Microsoft.DirectX.Direct3D.Pool pool ) public Texture ( Microsoft.DirectX.Direct3D.Device device, System.IO.Stream data, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.Pool pool ) Первый конструктор принимает указатель IntPtr, который является неуправляемым указателем СОМ интерфейса для IDIRECT3DTEXTURE9.

Такой шаг используется для достижения функциональной совместимос­ ти с неуправляемым кодом. Следующий конструктор позволяет созда­ вать пустую текстуру («blank» texture), определяя высоту, ширину и зна­ чение уровня детализации. Это иногда более предпочтительно, чем счи­ тывание этих значений из файла. Последний конструктор напоминает тот, который мы использовали в нашем приложении, только использует по­ ток данных быстрее, чем растровый bitmap объект. Данный поток дол­ жен иметь возможность загружаться в объект System.Drawing.Bitmap для работы этого конструктора. Существуют и другие интересные функции для загрузки текстуры в класс TextureLoader, это мы рассмотрим в после­ дующих главах.

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

Часть I. Введение в компьютерную графику // Draw our boxes DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.Of, angle / (float)Math.PI / 4.Of, O.Of, O.Of, O.Of, tex);

'DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.Of, angle / (float)Math.PI * 4.Of, 5.Of, O.Of, O.Of, texl);

DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.Of, angle / (float)Math.PI / 2.Of, -5.Of, O.Of, O.Of, tex2);

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

Рис. 3.3. Текструрированные кубы Краткие выводы В этой главе мы применили более эффективные методы отображения к нашим приложениям и использовали процедуру рендеринга, а также.

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

Глава 3. Введение в технологию рендеринга • Создали текстуры для более реалистичного отображения объек­ тов.

• Включили данные в наш вершинный буфер и отобразили тексту­ ры на примитивах.

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

76 Часть I. Введение в компьютерную графику Глава 4. Более совершенные технологии рендеринга Теперь, когда мы ознакомились с основными принципами технологии рендеринга, мы можем усовершенствовать методы отображения рисун­ ков. В этой главе мы рассмотрим следующее.

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

• Использование индексных буферов для более управляемого рен­ деринга.

Использование буферов глубины — Z-буферов для более реалис­ тичного рендеринга.

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

• PointList — набор точек, тип примитива с самоописанием, ото­ бражает на экране объект из набора точек. Вы не можете исполь­ зовать этот тип, когда рисуете индексированные примитивы (ко­ торые мы рассмотрим позже в этой главе), рис.4. LineList — набор линий, отображает каждую пару вершин как от­ дельную линию. При использовании этого типа примитива необ­ ходимо иметь для соединения четное число вершин (по крайней мере, две), рис.4.2.

• LineStrip — набор ломаных линий, отображает вершины в виде отдельной ломаной линии. После того как прорисовывается пер­ вая линия, замыкающая первую пару вершин, каждая последую­ щая линия рисуется из последней точки предыдущей линии. Вам нужно иметь по крайней мере две вершины при использовании этого типа примитива, рис.4. • TriangleList—набор треугольников, данный примитив мы исполь­ зовали до настоящего момента. Отображает набор трех вершин как отдельный, изолированный треугольник. Отбор невидимой по­ верхности определяется текущим состоянием рендера отбора, рис.4.4.

Глава 4. Более совершенные технологии рендеринга Рис. 4.1. Набор точек [~vo V \ V \ V V1 \ V Рис. 4.2. Набор линий Рис. 4.3. Набор ломаных линий 78 Часть I. Введение в компьютерную графику Рис. 4.4. Набор треугольников TriangleStrip — полоска из треугольников, рисует каждый следу­ ющий треугольник, используя при построении две имеющиеся вершины предыдущего треугольника. Режим отбора автоматичес­ ки отражает изображение всех четных треугольниках. Поскольку данный тип использует последние две вершины предыдущего тре­ угольника, вершина полученного треугольника будет расположе­ на с противоположной стороны от соединяющей эти вершины ли­ нии. Это наиболее распространенный тип примитива для слож­ ных трехмерных объектов, рис.4.5.

TriangleFan — веер из треугольников, похож на предыдущий тип TriangleStrip, за исключением того, что все треугольники исполь­ зуют одну общую вершину, рис.4.6.

Рис. 4.5. Полоска из треугольников При рисовании мы можем использовать одни и те же данные о верши­ нах для любого из этих типов примитивов. Приложение Direct3D интер­ претирует данные вершин каждый раз по-разному, в зависимости от типа примитива, который вы ему сообщаете. Давайте запишем «быстрый бит» кода для рисования различных типов примитивов.

Глава 4. Более совершенные технологии рендеринга •V •V •V • V >V Рис. 4.6. Веер из треугольников Типовая программа, включенная в CD диск, использует вершинный буфер, который мы создавали в главе 3. Отметим изменения в этой про­ грамме. Поскольку этот код не предусматривает перемещения вершин, функция преобразования в методе SetupCamera была удалена, также как и все ссылки на значение угла поворота «angle». Затем была добавлена следующая константа, определяющая число элементов:

private const int NumberItems = 12;

Значение «12» выбрано произвольно, но с учетом некоторых условий.

Слишком много вершин выглядело бы сейчас громоздко на экране, одна­ ко, все-таки желательно иметь достаточное число вершин, а также чтобы оно было четным и делилось на три. В этом случае мы можем корректно использовать примитивы LineList и TriangleList. Затем необходимо изме­ нить алгоритм создания вершинного буфера и функцию OnVertex BufferCreate, как приведено в листинге 4.1:

Листинг 4.1. Создание случайных вершин.

vb = new VertexBuffer(typeof(CustomVertex.PositionColored), Numberltems, device, Usage.Dynamic \ Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);

// Create a list of vertices equal to the number of items CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[Numberltems];

7 Randomly select the position of this vertex.. Keep it within a range of 5.

for(int i = 0;

i < Numberltems;

i++) 80 Часть I. Введение в компьютерную графику float xPos = (float)(Rnd.NextDouble() * 5.Of) - (float)(Rnd.NextDouble() * 5.0f);

float y?os = (float) (Rnd.NextDouble() * 5.Of) - (float) (Rnd.NextDouble() * 5.Of);

Pages:     || 2 | 3 | 4 | 5 |   ...   | 6 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.