D3D:RM
представляет собой набор API для быстрого
программирования 3D графики. "Если вы
хотите создать 3D-мир и манипулировать им в
реальном времени, вы должны использовать
D3D:RM" - это слова Microsoft'а. Ну должны мы или
не должны - это еще вопрос, но реальность
заключается все-таки в том, что D3D:RM - это
реальный инструмент создания трехмерной
программы, будь то игра, серьезная
графическая программа или что-либо еще.
Если вы
уже заметили :) Direct3D имеет еще один
подраздел - Direct3D:Immediate Mode - это уж совсем
низкоуровневые API. Я не рекомендую их
использование, если вам необходимо просто
научиться делать 3D графику. К слову сказать,
D3D:RM не входит в разряд DirectX Foundation, так как
сам он основан на D3D:IM
Что
хорошо, D3D:RM практически все равно, какую
видеокарту вы ему подсунете, главное, чтобы
для нее был установлен подходящий драйвер.
Это обуславливает легкую возможность
использования аппаратного ускорения.
D3D:RM
имеет набор инструментов для загрузки и
манипулирования так полюбившегося всем
формата трехмерных моделей .X - родного
формата DirectX. Описание этого формата я
обязательно дам в этой серии. Его удобство
заключается в том, что с помощью небольшой
утилиты 3dsconv, вы можете конвертить модели из
популярнейшего формата Autodesk 3D Studio - 3DS в
формат X. Хоть 3D Studio устарел, но вы можете
рисовать свои модели в Kinetix 3D Studio MAX,
экспортировать их в 3DS и получать .X Вобщем,
без моделей вы не останетесь.
Почему-то
Microsoft прекратил разработку Direct3D:RM и этот
интерфейс остался замороженным на уровне
DirectX6. Это создает ряд неудобств для
программиста на VB, так как чтобы
программировать 3D графику в полном экране (и
соответствнно использовать максимум 3D
ускорения) надо использовать интерфейс
DirectDraw4 вместо DirectDraw7. DirectDraw4 (а вместе с ним и
все интерфейсы и команды, в которых есть
определитель 4) относятся к DirectX6. Хорошо, что
еще хоть какая-то поддержка осталась и
можно спокойно и без особых фокусов с
тормозными tlb переведенными с Си
программировать 3D графику.
Если же вы хотите делать 3D в окне, вам опять
придется использовать интерфейсы DD7. Кроме
того, DD7 не может работать с дополнительными
видеокартами вообще. (DD просто не создается
под GUID второй видеокарты). Приходится опять
использовать DD4 и отсюда жуткая неразбериха
:(
Что
серьезного можно написать на Direct3D:
Хочу вас сначала огорчить, а потом утешить.
С помощью VB вы скорее всего вряд ли напишете
игру мирового уровня (слишком тормозит VB).
Но зато маленькую и непривередливую игру вы
можете написать легко. Также, вы можете
писать деловые программки, использующие
трехмерную графику, однако опять же,
серьезный графический редактор у вас не
выйдет. В будущем, эта ситуация может
измениться. На машине класса Pentium-не-MMX с
Direct3D лучше даже не возиться. Чем больше у
машины мозгов, тем лучше. Также, хотелось бы,
(если комп слабоват) чтобы у вас имелся
какой-никакой ускоритель.
Известные
игры на движке Direct3D - Весь Tomb Raider, Battle Zone 1-2, NFS,
Dark Omen.
Direct3D
более подходит для деловых программ, чем
OpenGL - 3D редакторы тому пример.
В Direct3D
положительное вращение происходит по
часовой стрелке вокруг какой-либо из осей,
обращенных к наблюдателю.
Direct3D
также может использовать U- и V- координатную
систему. Она также известна под названием текстурная
координатная система. Она называется так,
потому что используется при наложении
текстур на объект. Вектор V описывает
направление о ориентацию текстуры и лежит
вдоль оси Z. Вектор U (или верхний вектор)
обычно лежит вдоль очи Y с началом в [0,0,0].
Большей информации об этой координатной
системе нам пока не потребуется.
Трехмерные
трансформации - это базовые действия,
которые вы производите с объектами. Они
выражают местонахождение объекта,
относительно других объектов; вращение,
движение, изменение размеров объектов;
изменение позиции обзора, направления и
перспективы.
Вы трансформируете любую точку в другую точку, используя матрицу 4x4. В следующем примере, матрица используется для переопределения точки (x, y, z) в новую точку (x', y' z'):
Что
происходит при такой операции? Происходят
вот такие вычисления:
Самые
частые матрицы преобразования - это матрицы
перемещения, вращения и изменения размера.
Вы можете комбинировать эти матрицы для
получения одной матрицы, совершающей сразу
несколько таких действий. Например, вы
можете создать матрицу для одновременного
перемещения и вращения набора точек.
Хотя
матрицы визуально выглядят как двухмерные
массивы, на самом деле тип D3DMATRIX (так эти
матрицы задаются) представляет собой
простой массив из 16 членов. Членами этого
типа в VisualBasic являются элементы single, так что
не путайте с элементами D3DVALUE из С++
К примеру, следующая матрица, программно
может быть представлена вот как:
Dim
mtrScale As D3DMATRIX
With mtrScale
.rc11 = s: .rc12 = 0: .rc13 = 0: .rc14 = 0
.rc21 = 0: .rc22 = s: .rc23 = t: .rc24 = 0
.rc31 = 0: .rc32 = 0: .rc33 = s: .rc34 = v
.rc41 = 0: .rc42 = 0: .rc43 = 0: .rc44 = s
End With
Следующие
матрицы применяются для проведения
трансформаций точек:
Эта
матрица перемещает точку (x, y, z) в новую
точку (x', y', z')
Следующие
матрицы определены для системы левой руки и
могут отличаться от того, что вы видели
раньше.
Эта
матрица вращает точку (x, y, z) вокруг оси X и
получает новую точку (x', y', z')
Эта
матрица вращает точку вокруг оси Y
Это
матрица для вращения точки вокруг оси Z
Греческая
фи обозначает угол вращения, указанный в
радианах. Вращение происходит по часовой
стрелке вокруг оси.
Эта
матрица изменяет масштаб точки (x, y, z), по
направляющим x-, y-, z- переводя ее в новое
место (x', y', z')
Трехмерные
объекты в Direct3D состоят из объединений.
Объединение - это набор поверхностей,
каждая из которых представляет собой
простой многоугольник. Основной
многоугольник - это треугольник. Хоть RM
приложения и могут указывать
многоугольники с более чем тремя вершинами,
система все равно переводит эти фигуры в
треугольники перед тем как отрендерить
объект. А вот IM приложения должны всегда
использовать треугольники.
Далее я
рассмотрю использование многоугольников в
наших приложениях:
Треугольники
- самый предпочтительный тип
многоугольника, потому-что они всегда
выпуклые и они всегда плоские - два
требования рендера к многоугольникам.
Многоугольник является выпуклым, если
линия, проведенная между двумя любыми
точками многоугольника будет находиться
внутри него.
Три
вершины многоугольника всегда образуют
плоскость, но легко создать неплоский
многоугольник, добавив еще одну вершину.
Каждая
поверхность в объединении имеет
перпендикулярный поверхности вектор
нормали, чье направление определяется
порядком, в котором установлены вершины, а
также типом координатной системы - правой
или левой руки. Если вектор нормали
указывает на обозревателя, то эта сторона
поверхности - передняя. В Direct3D только
передняя сторона поверхности видима.
Передней стороной является сторона, в
которой вершины определены по порядку
часовой стрелки.
Приложениям
Direct3D не надо указывать поверхности нормали;
система вычисляет их автоматически по мере
необходимости. Система использует
поверхности нормали в плоском затенении.
Для затенения по методу Фонга или Гуро, а
также для контроля освещения и текстурных
эффектов, система использует вершины
нормали.
В
плоском режиме затенения (затушевывания)
система дублирует цвет одной вершины на
другие поверхности примитива. В режимах
затенения Гуро и Фонга для придания
гладкого вида объекту используются вершины
нормали. В затенении по методу Гуро цвет и
интенсивность находящихся рядом фершин
интерполируется по разделяющему их
пространству. В затенении по методу Фонга
система вычисляет подходящие значения
затенения для каждого пиксела поверхности.
Примечание:
Метод Фонга в настоящее время не
поддерживается
В
плоском режиме затенения показанная
пирамида будет отображена с четкой
границей между соседними вершинами;
система будет генерировать автоматические
поверхности нормали. В режимах Гуро или
Фонга значения затенения будут
интерполироваться через вершину и в
конечном счете мы увидим кривую
поверхность.
В
добавок к тому, что метод Гуро позволяет
делать кривые поверхности, он еще и более
правильно отображает свет, падающий на
поверхность. Поверхность в плоском режиме
затенения выглядит однородным цветом. Зато
метод Гуро позволяет свету распределяться
по поверхности правильно. Это различие
особенно очевидно, когда поблизости от
поверхности находится точечный источник
света. Метод гуро является более
предпочтительным методом затенения для
большинства Direct3D приложений, однако он
требует больших вычислений, а
соответственно и тормозит программу.
Система
интерполирует характеристики вершин
треугольника на треугольнике, когда
происходит рендеринг поверхности.
Интерполируются следующие характеристики:
Цвет
Отражение
Туман
Альфа
Все
интерполируемые характеристики
треугольника изменяются текущим режимом
затенения:
Плоский:
Нет интерполяции. Вместо этого цвет первой
вершины в треугольнике применяется на всей
поверхности.
Гуро: Применяется линейная
интерполяция между всеми тремя вершинами
Фонг: Параметры вершин перевычисляются
для каждого пиксела поверхности, используя
текущее освещение. Однако метод Фонга в
настоящее время не поддерживается.
Интерполирование
цветовой характеристики и характеристики
отражения происходит по разному, в
зависимости от цветовой модели. в модели RGB
(D3DCOLOR_RGB) система использует красную,
зеленую и синюю компоненту в интерполяции.
В монохромной модели (D3DCOLOR_MONO) система
использует только синюю компоненту цвета
вершины.
Например,
если красная компонента цвета вершины 1
была 0.8 и красная компонента вершины 2 была
0.4, то в режиме Гуро и модели цвета RGB точка
находящаяся в середине между этими
вершинами при интерполяции будет иметь
красную компоненту 0.6
Альфа
компонента цвета обрабатывается, как
отдельная интерполируемая характеристика,
потому что драйвер устройства может
осуществлять эффект прозрачности двумя
разными способами: используя смешение
текстур или используя стипплинг (stippling).
Приложение
может использовать член lShadeCaps структуры
D3DPRIMCAPS для определения какой способ
интерполяции поддерживает текущее
устройство.
Вы
можете использовать ленты треугольников и
веер треугольников чтобы указать целую
поверхность без указания всех трех вершин
для каждого треугольника . Например,
требуются только семь вершин для
определения такой ленты треугольников:
Система
ипользует вершины v1, v2 и v3 для того, чтобы
нарисовать первый треугольник; v2, v4 и v3 для
второго и так далее. Обратите внимание, что
все треугольники должны быть нарисованы в
порядке часовой стрелки.
Веер
треугольников похож по структуре на ленту
треугольников, но здесь много
треугольников используют одну общую
вершину.
Система
использует вершины v1, v2, v3 для того, чтобы
нарисовать первый треугольник; v1, v3, v4 для
второго и так далее...
Оверлеи
находятся сверху всех других компонентов
экрана (обычно их используют для
отображения жизней, энергии и т.п. в играх).
Порядок в z-буферах определяет порядок, в
котором оверлеи перекрывают друг друга.
Оверлеи, не имеющие указанного z-порядка (порядка,
в котором они перекрывают друг друга) ведут
себя совершенно непредсказуемо, когда
покрывают одну и ту же часть главной
поверхности. Direct3D:RM не сортирует оверлей,
если у вас нет z-буфера. Оверлеи без
указанного z-порядка по умолчанию принимают
z-порядок 0 и появляются на экране в очереди,
в которой они рендерятся. Возможные z-значения
для оверлеев от 0 (просто лежит на главной
поверхности DirectDraw) до 4 миллиардов (самый
ближний к обозревателю). Оверлей с z-порядком
2 перекроет оверлей с z-порядком 1. У двух
оверлеев не можут быть одинаковый z-порядок.
В этой
части мы поговорим о различных видеокартах,
3D-акселераторах, уникальных
идентификаторах, а также напишем первую
программу, шаблон которой будем
использовать в дальнейшей работе.
Честно
говоря, я был поражен, когда узнал насколько
просто Direct3D организовавает доступ к
различным видеокатрам и акселераторам. Вот
тут то я понял все значение слова device-independent.
Мало того, я даже с удивлением обнаружил,
что моя главная видеокарта S3 Virge DX/GX на 2мб
еще и поддерживает Direct3D ускорение! (ну
поддерживает как может, естесственно, но
даже невооруженным глазом заметно:)
С
помощью нехитрых средств DirectX вы можете
легко выявить все доступные в системе
устройства, их возможности, чтобы попросить
пользователя выбрать какое-нибудь для
дальнейшей работы.
Все
операции Direct3D и DirectDraw одинаковы для всех
видеокарт. Даже если видеокарта не может на
аппаратном уровне выполнить требуемую от
нее операцию, DirectX сделает это за нее
программно. Однако, вы можете
проанализировать все возможности
видеокарты, чтобы установить наиболее
подходящие под нее установки своей
программы.
Direct3D
"подступается" к конкретному
графическому устройству через так
называемый Глобальный Уникальный
Идентификатор (GUID). Смысл его в том, что
каждое устройство в компьютере имеет "прилепленный
к себе" набор букв и цифр, которые служат
чем-то вроде нагрудного значка у
заключенных - для того, чтобы их ни с кем не
спутали. При инициализации каждый главный
объект DirectX (DirectDraw, Direct3D, DirectSound...) по
хорошему требует, чтобы ему ввели этот
самый GUID устройства. По этому GUID'у объект,
посоветовавшись с реестром, решает на какое
системное устройство (видеокарту, звуковую
карту...) ему направлять вывод.
Помните,
в Учебнике
по DirectDraw у нас была такая строка:
Set
dd=dx.DirectDrawCreate("")
Вот
обратите внимание на параметр,
передаваемый в скобках. Мы передаем пустую
строку. Однако в этом месте функция требует,
чтобы ей передали GUID видеокарты, для
которой создавать объект DirectDraw. Передавая
пустую строку вместо GUID мы говорим, что надо
использовать устройство по умолчанию, то-есть
драйвер устройство, активного сейчас (вы
ведь видите картинку на экране, значит
какая-то видеокарта у вас все равно есть... :)
Так вот,
если бы мы вместо этой пустой строки
подсунули функции идентификатор другой
видеокарты, то вся графическая информация
передавалась бы на эту карту. Что я и сделал,
чуть-чуть поиздевавшись над игрухой,
которую мы делали в Учебнике по DirectDraw - VBPong,
превратив ее в 3DFX-Pong
Но это
все DirectDraw. А как же дело обстоит с Direct3D? А так
же! Единственное различие состоит в том, что
для Direct3D вам надо передавать два GUID. Дело в
том, что передавая первый GUID вы определяете
видеокарту, с которой собираетесь работать,
а передавая второй GUID, вы определяете
устройство рендеринга этой видеокарты.
Устройство рендеринга - это аппаратное
устройство прорисовки примитивов на экране.
Его может просто не быть на аппаратном
уровне, и тогда вы вынуждены эмулировать
его программно. Такое устройство
называется Direct3D RGB Software Emulation. Ежели
чипсет видеокарты оснащен каким-нибудь
устройством ускорения 3D-графики, тогда
такое устройство называется Hardware Acceleration
Trough Direct3D HAL. HAL - (Hardware Abstraction Layer) - это
способ взаимодействия Direct3D и аппаратного
обеспечения карты.
Ну вот,
теорию я вроде оттараторил. Сейчас мы
займемся написанием программы, которая
будет работать как необходимая составная
часть всех наших дальнейших программ. В чем
она заключается? Если вы когда-нибудь
играли в Tomb Raider, то наверное видели, как
перед первым запуском игры на экран вылазит
такое окошко, где вы выбираете ваш
акселератор, разрешение экрана и кучу
других опций. Сейчас мы сделаем подобное эе
окошк, котрон будет вылазить перед
запусками основных программ с просьбой
выбрать способ рендеринга. (Ну, Tomb Raider мы
пока не пишем, поэтому наша программа будет
определять только доступные видеокарты, а
также устройства рендеринга этой
видеокарты).
Кроме
этого, мы начнем написание модуля mdlDirect3DRM, (аналогичного
по значению модулю mdlDirectDraw7) в котором мы
определим все объекты DirectX и важные функции,
необходимые нам для дальнейшей работы.
Стартуем!
Создайте
новый проект и добавьте к нему наши
стандартные библиотеки - Microsoft dx7vb.dll и Patrice
Scribe Win32.tlb
Добавили?
Теперь создайте новый модуль, свойству Name
которого присвойте mdlDirect3DRM. Стартовой форме
присвойте Name - frmDriver, Caption - "Выберите
драйвер". В свойствах проекта укажите
стартовым объектом не frmDriver, а Sub Main. Позже вы
поймете, зачем нам это понадобилось.
Сохраните проект.
Теперь
надо построить форму. Ориентируйтесь по
следующей картинке. Enable у cmdOK должно быть False
Приступим
к написанию кода.
Сначала
- mdlDirect3DRM. Внесите туда такие объявления:
Option
Explicit
Public
dx As New DirectX7 'Объект
DirectX
Public dd7 As DirectDraw7 'Объект
DD для оконного режима
Public dd4 As DirectDraw4 'Объект
DD для полноэкранного режима
Public d3d As Direct3D7 'Объект
D3D:IM
Public d3dRm As Direct3DRM3 'Объект
D3D:RM
Public driverGuid As String 'Используемая
видеокарта
Public renderGuid As String 'Используемое
устройство рендеринга видеокарты
Как я
говорил в самой первой части, DirectDraw7 не
поддерживает полноэкранного режима Direct3DRM и
поэтому приходится использовать старые
интерфейсы 4 из DirectX6. Объект D3D:IM будет
использоваться при определении
существующих устройств рендеринга. Объект
D3D:RM вообще-то не нужен для этой программы,
но я оставил его, чтобы вы знали, как он
определяется :)
Private
Sub Main()
frmDriver.Show vbModal
End Sub
Sub Main
нам пока здесь ничего не дает, но ее
необходимость будет ясна в дальнейших
программах.
Переходим
к форме. В модуль ее кода поместите такие
строки:
Dim
EnumVideo As DirectDrawEnum
'Список видеоустройств
Dim EnumDevice As Direct3DEnumDevices
'Список устройств рендеринга
Dim I As Integer
Здесь
мы объявляем парочку объектов, которые
потребуются нам для определения
существующих видеоустройств.
Private
Sub Form_Load()
'Получаем
список видеокарт
Set EnumVideo = dx.GetDDEnum
For I = 1 To EnumVideo.GetCount
cmbVideo.AddItem EnumVideo.GetDescription(I) 'Выписываем
все видеокарты
Next I
cmbVideo.ListIndex = 0
End Sub
Первый
оператор получает объект EnumVideo As DirectDrawEnum. В
этом объекте содержтся информация обо всех
найденных видеокартах. Нам надо ее оттуда
вытащить, что мы и делаем затем в цикле.
EnumVideo.GetCount дает количество найденных
видеокарт, а EnumVideo.GetDescription(I) получает
описание I-той видеокарты. В конце, мы
устанавливаем элемент Combo отображать
первую видеокарту. (Это сразу генерирует
событие cmbVideo_Click)
Private
Sub cmbVideo_Click()
txtDriver.Text = EnumVideo.GetName(cmbVideo.ListIndex + 1) 'Получаем
имя драйвера
cmdOK.Enabled = False 'Чтобы
не нажали OK когда не выбрано устройство
рендеринга
lstDevice.Clear
If cmbVideo.ListIndex = 0 Then
'Если основная видеокарта,..
'...то
смотрим какие на ней навешаны устройства
рендеринга
Set dd7 = dx.DirectDrawCreate(EnumVideo.GetGuid(cmbVideo.ListIndex +
1))
Set d3d = dd7.GetDirect3D 'Получим D3D:IM
Set EnumDevice = d3d.GetDevicesEnum
'Получим устройства рендеринга
For I = 1 To EnumDevice.GetCount
lstDevice.AddItem EnumDevice.GetDescription(I)
'Выписываем
все устройства
Next I
Set d3d = Nothing
Set dd7 = Nothing
Else
'Если выбрали дополнительную карту, то
подразумевается, что это акселератор
lstDevice.AddItem "3D Acceleration through Direct3D HAL"
End If
End Sub
Вторая
часть определения способа рендеринга -
выбор устройства рендеринга для выбранной
видекарты. Благодаря Microsoft, которая не
догадалась оставить метод получения Direct3D
из DirectDraw4, мы не можем определить устройства
рендеринга для дополнительных карт. Так что
остается уповать, что пользователь вставил
на место второй видеокарты какой-нибудь
акселератор, например, 3DFX.
Процесс
определения устройства рендеринга для
главной карты таков: создаем объект DirectDraw7
для главной видеокарты. Получаем из объекта
DirectDraw объект Direct3D:IM. Из последнего получаем
объект DeviceEnum As Direct3DEnumDevice, в котором
содержится информация о найденных
устройствах рендеринга. Мы получаем и
записываем ее аналогично информации о
видеокартах.
Ну вот.
Информацию мы получили - теперь последние
штрихи:
Private
Sub lstDevice_Click()
'Выбрали
устройство - теперь можно нажимать OK
cmdOK.Enabled = True
End Sub
Private
Sub cmdCancel_Click()
If MsgBox("Правда
выйти?",
vbYesNo, "Выбор
устройства")
= vbYes Then
End
End If
End Sub
Private
Sub cmdOK_Click()
'Записываем
выбранные
настройки
If cmbVideo.ListIndex = 0 Then 'Если
главная
видеокарта,
то...
'Дравер
по
умолчанию,
устройство
рендеринга
- которое
выбрали
mdlDirect3DRM.driverGuid = ""
mdlDirect3DRM.renderGuid = EnumDevice.GetGuid(lstDevice.ListIndex
+ 1)
Else 'Если
дополнительная,
...
'Драйвер
этой видеокарты, устройство рендеринга - по
умолчанию
'для
этой
карты
(3D-акселерация)
mdlDirect3DRM.driverGuid = EnumVideo.GetGuid(cmbVideo.ListIndex
+ 1)
mdlDirect3DRM.renderGuid =
"IID_IDirect3DHALDevice"
End If
Unload Me
End Sub
Private
Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
'Убиваем
все
что
можно
и
нельзя
Set EnumVideo = Nothing
Set EnumDevice = Nothing
Set d3d = Nothing
Set dd4 = Nothing
Set dd7 = Nothing
End Sub
При
нажатии на cmdCancel мы завершаем программу (насовсем).
При нажатии на cmdOK мы записываем в mdlDirect3DRM
выбранные значения GUID'ов (они получаются
через метод EnumVideo.GetGuid(Index) и EnumDevice.GetGuid(Index)), а
затем выгружаем форму выбора драйвера. При
этом подразуемвается, что Sub Main
перехватывает управление и продолжает
выполнение программы. В любом случает,
перед выгразкой формы мы уничтожаем все
объекты, которые создали.
Ну вот
вроде и все. Обратите внимание на строку
IID_IDirect3DHALDevice в задании GUID. Она обозначает
использование акселерации для устройства
по умолчанию.
Вы
можете загрузить
готовый проект (8k).
Прежде, чем
начать программировать Direct3D, давайте
познакомимся, что это такое и с чем его едят.
Вот как преподносит простому пользователю
свое детище сама Microsoft:
"Direct3D был
разработан для программирования игр
мирового класса и трехмерной интерактивной
графики для компьютера, работающего под
управлением Microsoft Windows. Его миссия - дать
устройство-независимый доступ к
программированию совсем различного видео-3D
оборудования. Таким образом, Direct3D
становится стандартом для
программирования 3D устройств."
Ну что сказать?
Цели своей они естесственно добились.
Сегодня 99.9% видеокарт прелестно работают с
Direct3D. Однако Microsoft есть Microsoft - и в интерфейсе
программирования Direct3D есть свои баги и
глюки. Результатом стало появление других
стандартных интерфейсов для
программирования 3D графики. Например,
весьма популярный сейчас OpenGL,
разработанный компанией Silicon Graphics. Многие
известные игры используют OpenGL движки, да
хоть тот же Quake II. Еще один известный
интерфейс - Glide, бал разработан компанией 3DFX
Interactive и поддерживается только
видеоакселлераторами на базе ихнего
чипсета. Для всех этих интерфейсов
существуют свои SDK с их помощью любой может
программировать 3D графику.
Однако
вернемся к D3D. Как вы может быть слышали,
существует Direct3D Retained Mode и Direct3D Immediate Mode. Что
это такое? Сейчас расскажу.
В этом
учебнике, мы будем изучать Direct3D Immediate Mode.
D3DIM основан на
вершинах, многоугольниках и командах,
которые ими управляют. Проще не придумаешь.
Команды дают доступ к действиям над
вершинами и многоугольниками -
трансформации, освещению, наложению
текстур.
Эмуляция. По
своей сути, D3D предназначен для работы с 3D
акселлераторами. Однако, если конечный
пользователь не обладает этой небольшой
платкой, Direct3D позволяет программно
эмулировать функции, которые по-хорошему
выполняются на аппаратном уровне. Не мне
вам говорить что при этом получается.
Программист может при этом говорить, какие
функции D3D должен эмулировать.
Архитектуру
работы D3D можно изобразить вот на таком
рисунке:
Как видите D3D является как бы "прослойкой",
которая позволяет общаться приложению Win32 с
видеоаппаратурой при помощи
стандартизированных функций. Кроме того,
есть еще HAL (hardware-abstraction layer) это связка
устройство-драйвер. Благодаря ей,
приложение освобождается от выполнения
специфических команд устройства.
DirectX работает с HAL и поэтому когда что-либо
сбоит служба технической поддержки
корпорации Microsoft первым делом спрашивает
самые ли свежие драйверы стоят у
пользователя.
Direct3D тесно
связан с DirectDraw компонентом DirectX.
Поверхности DirectDraw используются, как
целевые для рендеринга (т.е. на них
происходит рендеринг) и как z-буфера.
Итак, если Вы
готовы, начнем!
Сделаем
маленькое приложение-пример, называемое
"Треугольник". Как не трудно
догадаться, оно будет рендерить сцену с
треугольником.
Откройте Visual
Basic IDE, создайте новый проект, назовите его
Triangle и подключите в "References" библиотеку
DirectX 7 For Visual Basic. Теперь вы можете работать с
DirectX.
Установите свойства главной формы
наподобии этим:
BorderStyle
= 1 'Fixed Single
Caption = "Triangle"
MaxButton = 0 'False
MinButton = 0 'False
ScaleHeight = 213
ScaleMode = 0 'User
Scale Width = 254
StartUpPosition = 3 'Windows Default
Чтобы показать
окно приложения на экране, добавьте
следующую строку в процедуру Form_Main:
Me.Show
Теперь, когда
форма готова, можно приступать к созданию
объектов DirectX.
ндамент
возведен. Теперь можно со спокойной
совестью переходить к построению графики!
После создания
окна приложения, первым объектом, который
мы создадим будет объект Direct Draw. Да-да, без
DirectDraw нам не обойтись, поэтому если Вы еще
не изучали, что это такое, обратитесь лучше
сначала к соответствующей статье Уголка
DirectX. Объект DirectDraw понадобится для
установки уровня приоритета приложения (cooperative
level), а также он будет использован для
создания буферов: главной поверхности и
заднего буфера, на который будет
происходить рендеринг.
Эти
определения поместите в раздел Declarations:
Dim
g_dx As New DirectX7
Dim g_dd As DirectDraw7
Dim g_ddsPrimary As DirectDrawSurface7
Dim g_ddsBackBuffer As DirectDrawSurface7
Dim g_ddsd As DDSURFACEDESC2
Dim pcClipper As DirectDrawClipper
Dim g_rcSrc as RECT
Dim g_rcDest as RECT
Итак, создадим
объект DirectDraw, как показано далее:
Private
Sub InitDDraw()
'
Создать объект DirectDraw и установить уровень
отношений
' приложения с другими.
Set
g_dd = g_dx.DirectDrawCreate("")
Эта строка
кода создает объект DirectDraw путем вызова
глобальной функции DirectDrawCreate. В качестве
параметра эта функция содержит пустую
строку, что означает, что создается объект
DirectDraw для активного драйвера дисплея. Для
оборудования, не поддерживающего GDI,
например 3D ускорителей, работающих с
отдельной 2D картой, необходимо точно
указать глобальный уникальный
идентификатор (GUID) желаемого драйвера, то
есть указать, какой драйвер устройства вы
хотите использовать.
Приложение
Triangle устанавливает уровень совместного
использования ресурсов с помощью метода
DirectDraw7.SetCooperativeLevel. Так мы скажем системе о
том, в каком режиме мы хотим работать - в
полноэкранном или оконном. (Учтите, что
некоторое оборудование не может работать в
оконном режиме. Вы можете обнаружить такое
оборудование через отсутсвие флага
DDCAP2_CANRENDERWINDOWED при вызове DirectDraw7.GetCaps). Режим,
который уже установлен перед вызовом
функции также называется "нормальным".
Вы можете указать его через флаг DDSCL_NORMAL,
передаваемый в SetCooperativeLevel. Однако этот
метод может дать сбой, если уже запущено
приложение работающее в полнооконном
эксклюзивном режиме.
g_dd.SetCooperativeLevel
Me.hWnd, DDSCL_NORMAL
После того, как
вы создали объект DirectDraw и определили
совместный режим, надо создать буфера,
которые будут содержать главную
поверхность и отрендеренную сцену.
Наше
приложение будет работать в окне. Вообще,
оконный режим работает медленнее, чем
полноэкранный - это факт. Кроме того, в
оконном режиме недоступны различные
функции полноэкранного режима. Но пока нам
этого и не требуется. Создадим для начала
главную поверхность.
'
Подготовить и создать главную поверхность
g_ddsd.lFlags = DDSD_CAPS
g_ddsd.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE
Set g_ddsPrimary = g_dd.CreateSurface(g_ddsd)
Описание
главной поверхности не содержит информацию
о размерах или глубине цвета.
Принимается текущий режим дисплея. После
создания главной поверхности, создадим
буфер для рендеринга. В случае нашего
приложения-примера, буфером будет
отдельная внеэкранная поверхность, которая
создается следующим образом:
'
Теперь, создаем буфер для рендеринга. Мы
повторно используем здесь g_ddsd
g_ddsd.lFlags = DDSD_HEIGHT Or DDSD_WIDTH Or DDSD_CAPS
g_ddsd.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN Or DDSCAPS_3DDEVICE
'Используем
размер формы для установки размера буфера
g_dx.GetWindowRect Me.hWnd, g_rcDest
'Установить
размерность описания поверхности
g_ddsd.lWidth = g_rcDest.Right - g_rcDest.Left
g_ddsd.lHeight = g_rcDest.Bottom - g_rcDest.Top
'Создать
поверхность для рендеринга
Set g_ddsBackBuffer = g_dd.CreateSurface(g_ddsd)
'Кэшировать
размеры цели рендеринга. Мы будем
использовать это для
' операции блиттинга
With g_rcSrc
.Left = 0
.Top = 0
.Bottom = g_ddsd.lHeight
.Right = g_ddsd.lWidth
End With
Предыдущий код
создает внеэкранную поверхность,
эквивалентную по размерам окну приложения.
Как показано в этом коде, вы должны включить
возможность DDSCAPS_3DDEVICE для любой поверхности,
которая будет использована, как целевая для
рендеринга. Эта возможность дает указание
системе выделить дополнительные
внутренние структуры данных для 3D
рендеринга.
После создания
главной поверхности и поверхности для
рендеринга, вы можете создать и прикрепить
объект DirectDrawClipper к главной поверхности.
Использование этого объекта определяет
экранные границы, освобождая вас от
обработки случаев, когда окно приложения
частично закрыто другим окном или окно
частично находится за пределами экрана.
Таким образом, клипперы не являются
необходимыми для полноэкранных приложений.
Следующий код создает клиппер и
прикрепляет его к главной поверхности:
'
Создать DirectDrawClipper и прикрепить его к
главной поверхности
Set pcClipper = g_dd.CreateClipper(0)
pcClipper.SetHWnd Me.hWnd
g_ddsPrimary.SetClipper pcClipper
End Sub
Создав все
объекты DirectDraw вы можете приступать к
созданию объектов Direct3D, которые
понадобятся для рендеринга сцены.
Эти
определения, относящиеся к объектам D3D,
поместите в раздел Declarations
Dim
d3d As Direct3D7
Dim g_d3dDevice As Direct3DDevice7
Dim VPDesc As D3DVIEWPORT7
Dim g_d3drcViewport(0 To 0) As D3DRECT
Dim ddsd As DDSURFACEDESC2
Создадим
объект Direct3D:
Sub
InitD3D()
'
Получить ссылку на класс Direct3D7 из
' объекта DirectDraw7
Set d3d = g_dd.GetDirect3D
После создания
объекта Direct3D7 вы должны создать устройство
рендеринга, используя метод Direct3D7.CreateDevice.
Этот метод подтверждает глобальный
уникальный идентификатор (GUID) желаемого
устройства и объект DirectDrawSurface7 (в нашем
случае g_ddsBackBuffer), на который будет
происходить рендеринг. Эта поверхность
должна быть создана, как 3D устройство путем
включения при его инициализации
возможности DDSCAPS_3DDEVICE.
Однако перед
тем, как создавать устройство рендеринга,
надо проверить на совместимость с режимом
дисплея. Если дисплей установлен в режиме
палитры (т. е. палитра цветов количество 256 и
меньше), наше приложение выйдет. Попытка
создать устройство для поверхности с
палитрой без ассоциирования его с палитрой
приведет к краху. Пока мы не будем
рассматривать режимы работы с палитрой для
удобства. Нормальное приложение должно
прикреплять свою палитру к поверхности для
рендеринга или требовать, чтобы экран был
установлен в режим не менее 16-битного цвета.
g_dd.GetDisplayMode
ddsd
If ddsd.ddpfPixelFormat.lRGBBitCount <= 8 Then
MsgBox "Это приложение не может работать в
режимах " & _
"цветности экрана меньше 16 бит."
End
End If
Затем,
приложение создает устройство рендеринга:
Set
g_d3dDevice = d3d.CreateDevice("IID_IDirect3DHALDevice",
g_ddsBackBuffer)
Вызов
Direct3D7.CreateDevice может дать сбой по многим
причинам. Самая частая из них - это когда
главная видеокарта не поддерживает свойств
3D. Другая возможность - когда оборудование,
управляющее дисплеем не может совершать
рендеринг в текущем режиме экрана. Все эти
возможности, по-хорошему, должны быть
отловлены при подготовке к созданию
объекта. Однако, дял поддержания простоты
кода примера, все возможности здесь не
учтены.
Обратите
внимание, что хотя CreateDevice и использует
объект DirectDrawSurface7, он создает устройство,
которое работает отдельно от объекта
поверхности DirectDraw. Устройство рендеринга
использует поверхность DirectDraw как цель для
совершения своих операций.
После того, как
устройство рендеринга создано, вы можете
создать объект Viewport и назначить его
устройству. Коротко, этот объект определяет,
как геометрия в 3D сцене обрезается и
представляется в 2D пространство экрана.
Процесс
создания объекта Viewport начинается с
установки параметров в тип D3DVIEWPORT7. Наше
приложение устанавливает параметры Viewport
соответственно размерам поверхности для
рендеринга.
VPDesc.lWidth = g_rcDest.Right - g_rcDest.Left
VPDesc.lHeight = g_rcDest.Bottom - g_rcDest.Top
VPDesc.minz = 0#
VPDesc.maxz = 1#
Когда тип
параметров объекта Viewport готов, надо
передать параметры Viewport устройству
рендеринга.
g_d3dDevice.SetViewport
VPDesc
Теперь,
кэшируем параметры Viewport в структуру RECT; мы
будем использовать эту информацию позже.
With
g_d3drcViewport(0)
.X1 = 0
.Y1 = 0
.X2 = VPDesc.lWidth
.Y2 = VPDesc.lHeight
End With
End Sub
Наконец мы
создали все самые необходимые объекты DirectX,
которые понадобятся нам для работы. В
следующем шаге мы перейдем к объявлению
объектов, которые понадобятся для
рендеринга сцены.
Итак, вы
создали самые необходимые объекты для 3D
рендеринга (объект DirectDraw, устройство
рендеринга и объект Viewport). Следующая вещь,
которую надо будет сделать - установить
начальное состояние освещения и создать и
сконфигурировать материал. При
необходимости, вы можете изменить эти
установки позже.
Следующий код
прилагает начальное состояние освещения к
устройству рендеринга g_d3dDevice путем вызова
метода Direct3DDevice7.SetRenderState
Sub
InitScene()
g_d3dDevice.SetRenderState D3DRENDERSTATE_AMBIENT, g_dx.CreateColorRGBA (1#, 1#,
1#, 1#)
В своем первом
параметре, SetRenderState передает флаг
D3DRENDERSTATE_AMBIENT, ассоциируя вызов с
вещественным освещением. Второй аргумент
использует метод DirectX7.CreateColorRGBA для
установки красного, зеленого и синего
значений цвета соответственно в 1.0, 1.0, 1.0
Результирующий свет получается белым. Цвет
не использует альфа-компоненту, которая в
этом случае также установлена в 1.0
Используйте
тип D3DMATERIAL7 для установки свойств материала:
Dim
mtrl As D3DMATERIAL7
mtrl.Ambient.r
= 1#
mtrl.Ambient.g = 1#
mtrl.Ambient.b = 0#
'
Передать материал устройству.
g_d3dDevice.SetMaterial
mtrl
Только что
созданный материал не имеет свойств и не
может быть использован в рендеринге.
Показанный код изменяте свойтсва материала
через тип D3DMATERIAL7, описывая материал,
который будет отражать красную и зеленую
компоненту света. В результате, материал в
сцене получится желтого цвета.
Когда в работу вступают текстуры, материал
объекта пропускается или окрашивается в
белый цвет.
Теперь, когда
материал и свет заданы, следующей ступенью
будет задание матриц Мира, Обзора и
Проекции. Ориентируясь в этих матрицах,
система сможет правильно располагать
геометрию объектов, камеру и ориентацию
смотрящего.
Эти
определения матриц поместите в раздел
Declarations:
Dim
matWorld As D3DMATRIX
Dim matView As D3DMATRIX
Dim matProj As D3DMATRIX
Чтобы создать
матрицу Мира, объявите тип D3DMATRIX, matWorld и
создайте идентификацию матрицы. Затем,
используйте метод Direct3DDevice7.SetTransform с флагом
D3DTRANSFORMSTATE_WORLD, чтобы определить матрицу мира.
'
Матрица Мира контролирует позицию и
ориентацию многоугольников
' в мировом пространстве. Мы будем
использовать это позже, для
' того, чтобы вращать треугольник
g_dx.IdentityMatrix matWorld
g_d3dDevice.SetTransform D3DTRANSFORMSTATE_WORLD, matWorld
Чтобы создать
матрицу обзора, объявите тип D3DMATRIX, matView и
создайте идентификацию матрицы. Затем,
передайте координаты камеры в метод
DirectX7.ViewMatrix, чтобы создать матрицу обзора.
Используйте метод SetTransform, чтобы определить
матрицу обзора для устройства рендеринга.
'
Матрица обзора определяет позицию и
ориентацию камеры
' Здесь мы просто двигаем ее назад на 15
единиц вдоль оси Z
g_dx.IdentityMatrix matView
Call g_dx.ViewMatrix(matView, MakeVector(0, 0, -15), MakeVector(0, 0, 0), _
MakeVector(0, 1, 0), 0)
g_d3dDevice.SetTransform D3DTRANSFORMSTATE_VIEW, matView
Предыдущий код
использует функцию MakeVector, чтобы перевести
три точки в тип D3DVECTOR. Вот эта функция.
Поместите ее код отдельно!
Private
Function MakeVector(a As Double, b As Double, c As Double) As D3DVECTOR
Dim vecOut As D3DVECTOR
With vecOut
.x = a
.y = b
.z = c
End With
MakeVector = vecOut
End Function
Чтобы создать
матрицу проекции, повторите пункит с
созданием типа D3DMATRIX, затем передайте
обрезаемые плоскости и поля угла зрения в
метод DirectX7.ProjectionMatrix чтобы создать матрицу
проекции. Используйте метод SetTransform, чтобы
определить матрицу для устройства
рендеринга.
'
Матрица проекции определяет, как 3D сцена
проецируется на
' 2D цель рендеринга (заднюю поверхность).
g_dx.IdentityMatrix matProj
Call g_dx.ProjectionMatrix(matProj, 1, 1000, pi / 2)
g_d3dDevice.SetTransform D3DTRANSFORMSTATE_PROJECTION, matProj
End Sub
В этом коде
второй параметр передает в ProjectionMatrix
пердставление расстояния до ближайшей
обрезаемой плоскости в указаных
приложением единицах, а третий параметр
представляет расстояние до дальней
обрезаемой плоскости. Четвертый параметр -
это поле видимого угла зрения в радианах.
С матрицами
покончено, переходим к приготовлению
геометрии для нашей сцены. Приготовление
геометрии заключается в определении
четырех типоы D3DVECTOR. Тип вектора
используется для каждой из трех точек,
необходимых для задания треугольника и еще
один тип нужен для задания нормали в
трехмерном пространстве. Технически, вам не
обязательно задавать геометрию на этом
этапе, вы можете задать ее позже.
Dim
p1 As D3DVECTOR, p2 As D3DVECTOR, p3 As D3DVECTOR, vNormal As D3DVECTOR
Dim g_TriangleVert(5) As D3DVERTEX
Private
Sub InitGeometry()
'
Установить геометрию для треугольника
'
Установить значения для трех точек, чтобы
получить треугольник
p1.x = 0#
p1.y = 5#
p1.z = 0#
p2.x = 5#
p2.y = -5#
p2.z = 0#
p3.x = -5#
p3.y = -5#
p3.z = 0#
'
Создать вектор нормали, указывающий прмо на
Viewport
' Мы обратили Z координату номали, что
определило
' обратную сторону треугольника
vNormal.x = 0#
vNormal.y = 0#
vNormal.z = -1#
Метод
DirectX7.CreateD3DVertex использует члены типа D3DVECTOR,
чтобы заполнить тип D3DVERTEX, используя
индивидуальные значения, которые
представляют компоненты вершин. Заполните
массив глобального типа D3DVERTEX, g_TriangleVert
информацией о вершинах треугольника.
'
Создать 3 вершины для передней части
треугольника
g_dx.CreateD3DVertex p1.x, p1.y, p1.z, vNormal.x, vNormal.y, vNormal.z, 0, 0, _
g_TriangleVert(0)
g_dx.CreateD3DVertex p2.x, p2.y, p2.z, vNormal.x, vNormal.y, vNormal.z, 0, 0, _
g_TriangleVert(1)
g_dx.CreateD3DVertex p3.x, p3.y, p3.z, vNormal.x, vNormal.y, vNormal.z, 0, 0, _
g_TriangleVert(2)
'
Теперь, то же самое для задней части
треугольника
g_dx.CreateD3DVertex p3.x, p3.y, p3.z, vNormal.x, vNormal.y, -vNormal.z, 0, 0, _
g_TriangleVert(3)
g_dx.CreateD3DVertex p2.x, p2.y, p2.z, vNormal.x, vNormal.y, -vNormal.z, 0, 0, _
g_TriangleVert(4)
g_dx.CreateD3DVertex p1.x, p1.y, p1.z, vNormal.x, vNormal.y, -vNormal.z, 0, 0, _
g_TriangleVert(5)
End Sub
Этот фрагмент
кода находит три точки в трехмерном
пространстве, которые определяют
треугольник, стоящий вертикально
относительно плоскости Z=0.
После создания
всех относящихся к Direct3D объектов,
инициализации сцены, и определения
геометрии для сцены, вы можете начать
рендеринг сцены. Об этом и пойдет речь
дальше
После того, как
вы создали окно приложения, объекты DirectX и
подготовили сцену, вы готовы рендерить ее.
Рендеринг - это такой процесс
прорисовывания сцены, исходя из
геометрических фигур объектов на сцене с
накладыванием текстур, материалов и
освещения. Мы пока текстурами заниматься не
будем.
Цикл
рендеринга в приложении "Triangle"
использует создаваемые позже подпрограммы
RenderScene и FrameMove. Первая процедура выполняет
непосредственно рендеринг, а вторая
совершает трансформацию сцены, чтобы мы не
уснули со скуки, созерцая неподвижный
треугольник.
Private
Sub Run()
Do While g_bRunning = True
CNT = CNT + 1
RenderScene
FrameMove (CNT / 360)
g_dx.GetWindowRect Me.hWnd, g_rcDest
j = g_ddsPrimary.Blt(g_rcDest, g_ddsBackBuffer, g_rcSrc, DDBLT_WAIT)
If
j <> DD_OK
Then MsgBox "Не могу скопировать исходный
прямоугольник на поверхность назначения."
&
_
Chr$(13) & Hex(j)
End
End If
DoEvents
Loop
End Sub
Чтобы процесс
рендеринга не вошел в бесконечный цикл, мы
предусмотрели выход из него через
логическую переменную g_bRunning. Но об этом мы
поговорим в следующей части. А пока,
вернемся к рендерингу.
Прежде, чем еще
раз отрендерить сцену, ее надо обновить.
Немедленно, после возвращения процедуры
RenderScene, вызывается подпрограмма FrameMove. Эта
подпрограмма просто обновляет матрицу Мира,
от которой в Direct3D зависит геометрия модели.
Приложение отражает вращение вокруг оси Y.
Угол вращения передается процедуре через
параметр stepVal.
Private
Sub FrameMove(stepVal As Single)
Dim matSpinY As D3DMATRIX
Call g_dx.RotateYMatrix(matSpinY, stepVal)
Call g_d3dDevice.SetTransform(D3DTRANSFORMSTATE_WORLD, matSpinY)
End Sub
В реальности
же ваши приложения в этом месте обновления
сцены будут делать гораздо больше, чем
приложение вращения к объекту. В этом месте
происходят все сложные математические
расчеты и перемещения объектов, которые
пользователь потом видит на экране. В
сущности, чем комактнее и быстрее будет ваш
код в этом месте, тем быстрее приложение
сможет перейти к рендерингу кадра и тем
лучше будут показатели FPS (кадров в секунду).
Когда
геометрия модели обновлена и отражает
желаемый эффект анимации, вы можете
рендерить сцену. В нашем приложении этим
будет заниматься процедура RenderScene. Она
начинает с очистки объекта Viewport.
Private
Sub RenderScene()
'
Очистить
viewport в
голубой
цвет.
g_d3dDevice.Clear 1, g_d3drcViewport(), D3DCLEAR_TARGET, &HFF, 0, 0
Этот
код
вызывает
метод
Direct3DDevice7.Clear для
очистки
Viewport. Первые
два аргумента указывают на участок ли
участки на Viewport, которые должны быть
очищены и на их количество. В большинстве
случаев, как в этом приложении вы будете
очищать один участок, охватывающий целиком
весь объект Viewport. Следующий аргумент
говорить что очищать. Так как наше
приложение не использует глубинные буфера,
передается только флаг D3DCLEAR_TARGET. Последние
три параметра устанавливают значения
очистки для цели рендеринга и буферов.
После очистки, программа информирует Direct3D о
том, что начинается процедура рендеринга,
выполняет рендеринг, а затем сигналит, что
рендеринг закончен. Это показано здесь:
'
Начать, рендерить треугольник, затем
завершить
g_d3dDevice.BeginScene
Call g_d3dDevice.DrawPrimitive(D3DPT_TRIANGLELIST, D3DFVF_VERTEX, _
g_TriangleVert(0), 6, D3DDP_DEFAULT)
g_d3dDevice.EndScene
End Sub
Методы
Direct3DDevice7.BeginScene и Direct3DDevice7.EndScene говорят
системе о начале и конце рендеринга. Вы
можете помещать команды рендеринга только
между этими методами.
Наконец, все
подпрограммы закончены, осталось собрать
их воедино. Напишем еще две процедуры: одна
для обработки события Form_Load, другая -
наоборот Form_Unload.
Сначала,
объявите еще одну константу и пару
переменных:
Dim
g_bRunning As Boolean
Dim CNT As Integer
Dim j As Long
Теперь,
процедуры:
Private
Sub Form_Load()
Me.Show
InitDDraw
InitD3D
InitScene
InitGeometry
g_bRunning = True
Run
End Sub
Private
Sub Form_Unload(Cancel As Integer)
g_bRunning = False
End Sub
Теперь,
запустите программу, и если все правильно,
вы увидите вращающийся желтый треугольник
на голубом фоне.
Приятного
программирования, Antiloop