Что такое Direct3D:RM

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 редакторы тому пример.

Система 3D-координат

Все вы прекрасно знаете из школьной геометрии о картезианской координатной системе. Где существуют оси X (горизонтальная) и Y (вертикальная). Начало координат находится в месте пересечения этих осей, ось X направлена вправо, а ось Y - вверх. Трехмерная картезианская система добавляет к этим двум осям еще одну - ось Z. В трехмерной графике существуют две координатных системы: система левой руки и система правой руки. В обоих случаях, оси X и Y направлены как я уже говорил, а вот ось Z в системе левой руки направлена вдаль от вас, а в системе правой руки, ось Z направлена на вас. Direct3D (а следовательно будем и мы) использует систему левой руки. Как раз она-то и показана на рисунке.

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

Direct3D также может использовать U- и V- координатную систему. Она также известна под названием текстурная координатная система. Она называется так, потому что используется при наложении текстур на объект. Вектор V описывает направление о ориентацию текстуры и лежит вдоль оси Z. Вектор U (или верхний вектор) обычно лежит вдоль очи Y с началом в [0,0,0]. Большей информации об этой координатной системе нам пока не потребуется.

3D-трансформации

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

Вы трансформируете любую точку в другую точку, используя матрицу 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-буферах определяет порядок, в котором оверлеи перекрывают друг друга. Оверлеи, не имеющие указанного 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

Прежде, чем начать программировать 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