Вторник, 09 Августа 2022, 13:49

Приветствую Вас Гость

Меню сайта
Категории каталога
Создание игр [344]
Статьи об общих понятиях связанных с созданием игр.
Программирование [80]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [137]
Статьи о программах для создания игр, уроки и описания.
Софт [38]
Различные программы, в том числе в помощь игроделам.
2D-графика [12]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [13]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [5]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [159]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [123]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Игры какого типа вы предпочитаете делать?
Всего ответов: 16619
Главная » Статьи » Программирование

Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 1
1.1. Подготовка к работе
Для работы нам понадобится компьютер с операционной системой Windows не ниже Windows 98, установленный DirectX 8.1 или новее и Visual Basic версии 6. Крайне желательно иметь видеоадаптер, полностью поддерживающий DirectX 8.1. Из наиболее распространенных это ATI Radeon 8500 и выше, nVidia GeForce3, GeForce4 Ti и выше. Однако вполне возможна работа и на GeForce серии MX и, даже, Riva TNT.
Стоит остановиться на установке Microsoft DirectX 8.1 SDK for Visual Basic. В ходе установки будет выбор режима – Debug или Retail, если мы выберем Debug, у нас будет возможность эмулировать программно функции DirectX, не поддерживаемые видеоадаптером аппаратно. Особенно рекомендуется выбрать режим Debug, если видеоадаптер не имеет полной поддержки DirectX 8.1.
1.2. Инициализация Direct3D
Приступим, наконец, к нашему первому проекту. Загрузим Visual Basic и создадим новый проект, состоящий из одной формы. Теперь подключим к проекту библиотеку типов для работы с DirectX 8.1. Для этого в меню Проект откроем пункт Ссылки, найдем и отметим флажком строку DirectX 8 for Visual Basic Type Library.
Зададим глобальные переменные:
Code

Dim dx8 As New DirectX8
Dim d3d As Direct3D8
Dim d3dDevice As Direct3DDevice8

Объектные переменные dx8 и d3d содержат соответственно экземпляры классов DirectX8 и Direct3D8, которые нужны в основном на этапе инициализации, а экземпляр класса Direct3DDevice8 объектная переменная d3dDevice – это наш основной рабочий инструмент при работе с графикой.
Создадим процедуру для инициализации Direct3D:
Code

Private Sub D3DInit()
Dim DispMode As D3DDISPLAYMODE
Dim d3dpp As D3DPRESENT_PARAMETERS

  Set d3d = dx8.Direct3DCreate
  d3d.GetAdapterDisplayMode D3DADAPTER_DEFAULT, DispMode

  d3dpp.Windowed = True
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD
  d3dpp.BackBufferFormat = DispMode.Format

  Set d3dDevice = d3d.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Me.hWnd _
  , D3DCREATE_SOFTWARE_VERTEXPROCESSING, d3dpp)
End Sub

Рассмотрим работу процедуры подробнее. С помощью функции dx8.Direct3DCreate инициализируем переменную d3d, с помощью d3d.GetAdapterDisplayMode D3DADAPTER_DEFAULT, DispMode заполняем структуру DispMode данными о текущем видеорежиме. Вторая структура d3dpp заполняется параметрами создаваемого устройства d3dDevice. Windowed говорит о том, что наша программа будет работать в оконном режиме, в этом случае формат, естественно, должен соответствовать формату видеорежима – d3dpp.BackBufferFormat = DispMode.Format.
Примечание:
Вывод графики в Direct3D осуществляется не прямо на экран, а в специальную область памяти, называемую BackBuffer. Только после того, как изображение полностью сформировано, оно переносится из BackBuffer на экран.

Способ переноса BackBuffer на экран определяется полем SwapEffect структуры D3DPRESENT_PARAMETERS. Меняя значение SwapEffect можно, например, включить или выключить синхронизацию переноса изображения из BackBuffer на экран с частотой кадров.
Примечание:
Для корректного освобождения ресурсов все переменные объектного типа должны перед завершением программы уничтожаться присваиванием им значения Nothing.

Исходя из вышесказанного, сразу готовим процедуру для уничтожения объектов:
Code

Private Sub ClearAll()
  Set d3dDevice = Nothing
  Set d3d = Nothing
  Set dx8 = Nothing
End Sub

Добавим к глобальным переменным новую переменную Running типа Boolean, создадим процедуры Form_Load и Form_QueryUnload и внесем в них такой код:
Code

Private Sub Form_Load()
  Me.Show
  D3DInit
  Running = True
  Do While Running
  DoEvents
  Render
  Loop
  Unload Me
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
  If Running Then
  Cancel = 1: Running = False
  Else
  ClearAll
  End If
End Sub

При старте программы инициализируется Direct3D и запускается цикл с вызовом процедуры Render, в которой и будет вывод изображения, а такая структура процедуры Form_QueryUnload не дает завершить программу до выхода из цикла, ведь при завершении работы программы мы уничтожим созданные объекты, и вызов Render приведет к ошибке.
И, наконец, сама процедура Render:
Code

Private Sub Render()
  d3dDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, Rnd * &HFFFFFF, 1, 0
  d3dDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub

В ней выполняется всего два действия – очистка (закраска) BackBuffer случайным цветом и вывод полученного изображения на экран.
Наша минимальная программа, использующая Direct3D, готова. Жмем клавишу <F5> – и, если мы нигде не ошиблись, видим быстро мерцающую форму. Можно заменить в процедуре D3DInit строку:
Code

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD

на:
Code

d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC

При этом исчезнут полосы – мерцание формы станет равномерным, ведь мы засинхронизировали его с частотой кадров. На компакт-диске, прилагаемом к книге, содержится (в папке Pr01) полный код данного проекта, как и остальных проектов, описанных в книге.

1.3. Рисуем плоские фигуры.
Пора, наконец, нарисовать что-то существенное. В Direct3D, за редким исключением, вывод графики основан на выводе примитивов – серий треугольников. Треугольники выбраны потому, что на них можно разбить любой многоугольник или, как его еще называют, полигон.
Для однозначного задания положения треугольника в пространстве достаточно задать координаты трех его вершин. В Direct3D существует понятие «вертекс» – особая структура, содержащая координаты и некоторые другие характеристики точки (например, вершины треугольника) в пространстве.

Определение:
Вертекс – особая структура, содержащая координаты и некоторые другие характеристики точки в пространстве.

Возьмем за основу наш предыдущий проект. Добавим такую структуру:
Code

Private Type vFormat
  PosX As Single
  PosY As Single
  PosZ As Single
  RHW As Single
  Color As Long
End Type

Эта структура задает формат вертекса, то есть перечень характеристик точки, описанных в вертексе. Поля PosX, PosY и PosZ соответствуют трем координатам точки в 3D пространстве. Поле RHW задает особую характеристику, значение которой мы рассмотрим чуть позже, и поле Color задает цвет точки. Кроме структуры вертекса мы должны также задать способ его обработки, то есть мы указываем Direct3D, каким образом нужно обрабатывать вертекс. Для этого служат специальные константы, заданные в Enum CONST_D3DFVFFLAGS, их имена начинаются с «D3DFVF_»:
Code

Private Const vFlag = D3DFVF_XYZRHW Or D3DFVF_DIFFUSE

Такая комбинация констант «поясняет» Direct3D структуру нашего вертекса.
Добавим новые общие переменные:
Code

Dim vBuffer As Direct3DVertexBuffer8
Dim Vert(0 To 2) As vFormat
Dim vSize As Long

Массив Vert() для трех вертексов, переменная vSize содержащая размер вертекса в байтах и vBuffer, переменная нового для нас типа Direct3DVertexBuffer8. Напишем специальную процедуру для задания значений вертексов:
Code

Private Sub InitGeometry()
  vSize = Len(Vert(0))
  Set vBuffer = d3dDevice.CreateVertexBuffer(3 * vSize, 0, vFlag, D3DPOOL_DEFAULT)

  Vert(0).PosX = 10
  Vert(0).PosY = 10
  Vert(0).PosZ = 0
  Vert(0).RHW = 1
  Vert(0).Color = &HFF

  Vert(1).PosX = 210
  Vert(1).PosY = 10
  Vert(1).PosZ = 0
  Vert(1).RHW = 1
  Vert(1).Color = &HFF00&

  Vert(2).PosX = 10
  Vert(2).PosY = 210
  Vert(2).PosZ = 0
  Vert(2).RHW = 1
  Vert(2).Color = &HFF0000

  D3DVertexBuffer8SetData vBuffer, 0, vSize * 3, 0, Vert(0)
End Sub

Используя d3dDevice.CreateVertexBuffer инициализируем объектную переменную vBuffer, не забываем сразу добавить в процедуру ClearAll строку для ее уничтожения:
Code

Set vBuffer = Nothing

Функция D3DVertexBuffer8SetData переносит данные в вертексный буфер, откуда и будет происходить вывод вертексов при растеризации.
И, наконец, внесем изменения в процедуру Render:
Code

Private Sub Render()
  d3dDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, &H346666, 1, 0
  d3dDevice.BeginScene

  d3dDevice.SetStreamSource 0, vBuffer, vSize
  d3dDevice.SetVertexShader vFlag
  d3dDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 1

  d3dDevice.EndScene
  d3dDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub

Весь вывод графики в Direct3D должен начинаться с d3dDevice.BeginScene и заканчиваться d3dDevice.EndScene. С помощью d3dDevice.SetStreamSource 0, vBuffer, vSize указываем нашему устройству рендера (d3dDevice) на вертексный буфер, а d3dDevice.SetVertexShader vFlag конкретизирует формат его содержимого.
И само рисование - d3dDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 1. Это указание вывести список треугольников (TRIANGLELIST), начинающийся с адреса 0 и содержащий 1 треугольник.
Жмем <F5> – и видим градиентно раскрашенный треугольник.
Для пояснения смысла поля RHW в структуре vFormat проведем эксперимент. В процедуре InitGeometry заменим строку Vert(2).RHW = 1 на Vert(2).RHW = 3. В результате цвет от красной вершины (№ 2) как бы расползается более сильно, чем от остальных вершин. Если немного напрячь воображение, можно представить, что красная вершина находится к нам ближе. Использование поля RHW в формате вертекса означает использование «приведенного» формата, в такой формат Direct3D неявно преобразует геометрию из других форматов перед выводом.
Проведем еще один эксперимент. Добавим процедуру обработки события MouseMove для формы:
Code

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  If Button = 1 Then
  Vert(0).PosX = X
  Vert(0).PosY = Y
  D3DVertexBuffer8SetData vBuffer, 0, vSize, 0, Vert(0)
  End If
End Sub

Теперь мы можем перетаскивать синюю вершину мышкой. Заметьте, что при перемещении вершины за противоположную сторону треугольника сам треугольник пропадает. Дело в том, что Direct3D отображает по умолчанию только лицевую сторону треугольника. При использовании TRIANGLELIST лицевой считается та сторона, на которой вершины расположены по часовой стрелке. Это сделано с целью повышения быстродействия при выводе объемных объектов. Они, как правило, представлены своей поверхностью, и их внутренняя сторона не бывает видна ни при каких обстоятельствах. Если нам необходимо отобразить двухстороннюю фигуру, эту оптимизацию необходимо запретить. Добавим в D3DInit такую строку:
Code

d3dDevice.SetRenderState D3DRS_CULLMODE, D3DCULL_NONE

Теперь наш треугольник виден с двух сторон.
На компакт-диске, прилагаемом к книге, в папке Pr02 вы можете найти полный код данного проекта.

1.4. ZBuffer
Переделаем предыдущий проект для отображения двух треугольников. Для этого в InitGeometry добавим еще три вершины:

Code

Private Sub InitGeometry()
  vSize = Len(Vert(0))
  Set vBuffer = d3dDevice.CreateVertexBuffer(6 * vSize, 0, vFlag, D3DPOOL_DEFAULT)

  Vert(0).PosX = 250
  Vert(0).PosY = 250
  Vert(0).PosZ = 0.5
  Vert(0).RHW = 1
  Vert(0).Color = &H808080

  Vert(1).PosX = 210
  Vert(1).PosY = 10
  Vert(1).PosZ = 0.5
  Vert(1).RHW = 1
  Vert(1).Color = &H808080

  Vert(2).PosX = 10
  Vert(2).PosY = 210
  Vert(2).PosZ = 0.5
  Vert(2).RHW = 1
  Vert(2).Color = &H808080

  D3DVertexBuffer8SetData vBuffer, 0, vSize * 3, 0, Vert(0)

  Vert(0).PosX = 10
  Vert(0).PosY = 10
  Vert(0).PosZ = 0.1
  Vert(0).RHW = 1
  Vert(0).Color = &HFF

  Vert(1).PosX = 250
  Vert(1).PosY = 40
  Vert(1).PosZ = 0.5
  Vert(1).RHW = 1
  Vert(1).Color = &HFF00&

  Vert(2).PosX = 40
  Vert(2).PosY = 250
  Vert(2).PosZ = 0.9
  Vert(2).RHW = 1
  Vert(2).Color = &HFF0000

  D3DVertexBuffer8SetData vBuffer, vSize * 3, vSize * 3, 0, Vert(0)
End Sub

Первый треугольник получается серого цвета, а второй – разноцветный, как в предыдущем проекте. Изменим в обработке события MouseMove в функции D3DVertexBuffer8SetData значение Offset с 0 на vSize * 3 – адрес первой вершины второго треугольника, а в процедуре Render в методе DrawPrimitive увеличим счетчик треугольников до двух.
Теперь рисуется два треугольника, причем второй всегда закрывает собой первый. Это не меняется, как бы мы не меняли значение PosZ для вершин треугольников.
Почему так происходит? Неужели для корректной отрисовки всегда нужно упорядочивать треугольники? А если треугольники пересекаются – тут уже не поможет никакое упорядочивание.
Вспомним, что вывод графики сначала происходит в BackBuffer, а он двумерный, в нем нет места третьему измерению. Для того, чтобы обойти это ограничение, создан ZBuffer – такой же, как и BackBuffer двумерный участок памяти, в который при рисовании записывается не цвет точки, а расстояние до нее.
Определение:
ZBuffer – двумерный участок памяти, по размерам равный BackBuffer, в который при рисовании записывается не цвет точки, а расстояние до нее.

Как это может помочь? При выводе следующей точки вычисляется расстояние до нее и сравнивается с расстоянием, записанным в ZBuffer, если оно больше – точка не рисуется.
Приступим к созданию ZBuffer. В процедуру D3DInit до создания d3dDevice добавим две строки:
Code

d3dpp.EnableAutoDepthStencil = 1
d3dpp.AutoDepthStencilFormat = D3DFMT_D16

Первая указывает, что d3dDevice создается с ZBuffer, вторая устанавливает формат ZBuffer.
После создания d3dDevice добавляется еще одна строка:
Code

d3dDevice.SetRenderState D3DRS_ZENABLE, D3DZB_TRUE

Здесь мы «включаем» ZBuffer.
Запускаем программу и смотрим результат. Видим искаженное изображение двух треугольников. При перемещении искажения усиливаются. В чем дело? А дело в том, что ZBuffer запоминает свое состояние с предыдущего кадра. После того, как была отрисована какая-то точка, более далекая точка на этой позиции уже не может быть отображена не только в текущем кадре, но и в следующих. Чтобы от этого избавиться, ZBuffer необходимо очищать перед каждым кадром так же, как мы очищаем BackBuffer.
Добавляем в процедуре Render флаг очистки ZBuffer:
Code

d3dDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &H346666, 1, 0

Теперь все отображается корректно. Убедитесь в этом, удаляя и приближая вершины треугольников (меняя значение PosZ).
Код данного проекта находится на компакт-диске в папке Pr03.

1.5. 3D, перспектива, матрицы
Очевидно, что способ, которым мы в предыдущей главе рисовали треугольники, неудобен и неполноценен для 3D игр. Неудобен потому, что мы, построив сложную сцену, лишены возможности взглянуть на нее с другой точки. Камера всегда направлена вдоль оси Z так, что ось X направлена вправо. Чтобы посмотреть, например, вдоль оси X, необходимо пересчитать позиции всех вертексов сцены! То же и в случае масштабирования, придется пересчитывать позиции вертексов, и при смене разрешения экрана или размеров формы – опять предстоит перерасчет, ведь шкала жестко привязана к размеру пикселя.
Неполноценность заключается в отсутствии перспективы – видимый размер наших треугольников не зависит от расстояния до них (координаты Z). Чтобы избавиться от этих недостатков, нужно отказаться от «приведенного» формата вертекса. Но сначала немного теории.
В математике существует понятие «матрица» – таблица чисел, построенная по определенным правилам. Из всех типов матриц нас будет интересовать только один – квадратная (4 × 4) таблица чисел типа Single. Фактически это таблица коэффициентов в системе уравнений, которая описывает почти любое изменение (трансформацию) пространства. Это прежде всего перемещение и масштабирование – наиболее простые трансформации, это также поворот вокруг любой из осей координат или вокруг произвольной оси. Матрицей можно описать перспективу – изменение видимого размера объекта в зависимости от расстояния до него. Кроме того, одной единственной матрицей можно задать любое сочетание всех перечисленных трансформаций. Правда есть некоторые ограничения – после любого преобразования прямая останется прямой (либо выродится в точку), а плоскость – плоскостью (либо выродится в прямую или точку).
Каким образом произвести расчет нужной матрицы? Ответ на этот вопрос может дать курс аналитической геометрии, но мы не будем углубляться в дебри науки, так как, к нашей радости, в составе DirectX уже имеется набор готовых функций.
Каким образом применять матрицы? d3dDevice содержит несколько трансформаций, которые задаются с помощью метода SetTransform, рассмотрим три основные:


  • Первая – трансформация мира, обозначаемая D3DTS_WORLD. Записывая в неё матрицу, например, перемещения, мы вызываем соответствующее перемещение всей выводимой в дальнейшем геометрии.
  • Вторая – трансформация камеры или обзора D3DTS_VIEW. Её действие противоположно трансформации мира. Записывая в D3DTS_VIEW матрицу вращения вокруг оси Y на 30 градусов, мы получим вращение мира на -30 градусов, что соответствует повороту камеры (иначе говоря, глаз наблюдателя) на 30 градусов. То есть с помощью этой трансформации мы задаем точку, из которой смотрим на наш 3D мир, и направление взгляда.
  • Третья – трансформация проекции D3DTS_PROJECTION. Она задает проекцию изображения на экран монитора (точнее на BackBuffer). Это может быть ортогональная проекция или перспектива, левосторонняя или правосторонняя.

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

Dim Mtrx As D3DMATRIX

Из формата вертекса исключим поле RHW:
Code

Private Type vFormat
  Pos As D3DVECTOR
  Color As Long
End Type

Поле Pos соответствует трем старым полям PosX, PosY, и PosZ.
Соответственно изменим флаговое описание вертекса:
Code

Private Const vFlag = D3DFVF_XYZ Or D3DFVF_DIFFUSE

Добавим функцию для быстрого создания векторов:
Code

Private Function vec3(X As Single, Y As Single, Z As Single) As D3DVECTOR
  vec3.X = X
  vec3.Y = Y
  vec3.Z = Z
End Function

Использование «неприведенного» формата вертекса подразумевает использование света по умолчанию. Поскольку свет мы еще не изучили – отключаем:
Code

d3dDevice.SetRenderState D3DRS_LIGHTING, 0

И процедура, задающая начальные значения трансформаций:
Code

Private Sub InitMatrix()
  D3DXMatrixIdentity Mtrx
  d3dDevice.SetTransform D3DTS_WORLD, Mtrx

  D3DXMatrixPerspectiveFovLH Mtrx, 3.141593 / 4, Me.ScaleHeight / Me.ScaleWidth, 0.1, 10
  d3dDevice.SetTransform D3DTS_PROJECTION, Mtrx

  D3DXMatrixLookAtLH Mtrx, vec3(0, 0, -2), vec3(0, 0, 0), vec3(0, 1, 0)
  d3dDevice.SetTransform D3DTS_VIEW, Mtrx
End Sub

Рассмотрим ее подробнее. С помощью D3DXMatrixIdentity рассчитывается матрица «нулевого» преобразования (или идентичности) и в следующей строке записывается в трансформацию мира. Это означает, что координаты, указанные в вертексе, будут использоваться без каких-либо изменений, как есть.
С помощью D3DXMatrixPerspectiveFovLH рассчитывается матрица левосторонней перспективы, описывающая камеру с углом зрения 45º по вертикали (значение 3.141593 / 4 указано в радианах), и с отношением размера по вертикали к размеру по горизонтали, соответствующим размеру формы. Последние две величины – ограничение ближнего и дальнего планов видимости, зачем это нужно – рассмотрим чуть позже. Эта матрица записывается в трансформацию проекции.
И остается трансформация обзора, D3DXMatrixLookAtLH позволяет задать позицию наблюдателя в пространстве (первый вектор), направление его взгляда (второй вектор) и направление «вверх» (третий вектор). В нашем примере наблюдатель из точки «0, 0, -2» смотрит в начало координат. Направление «вверх» соответствует направлению оси Y.
Теперь зададим геометрию:
Code

Private Sub InitGeometry()
  Set vBuffer = d3dDevice.CreateVertexBuffer(2 * 3 * vSize, 0, vFlag, D3DPOOL_DEFAULT)

  Vert(0).Pos = vec3(-0.5, -0.5, 0)
  Vert(0).Color = &HFF

  Vert(1).Pos = vec3(0.5, -0.5, 0)
  Vert(1).Color = &HFF

  Vert(2).Pos = vec3(0, 0.5, 0)
  Vert(2).Color = &HFF

  D3DVertexBuffer8SetData vBuffer, 0, vSize * 3, 0, Vert(0)

  Vert(0).Pos = vec3(0, -0.5, -0.5)
  Vert(0).Color = &HFF0000

  Vert(1).Pos = vec3(0, -0.5, 0.5)
  Vert(1).Color = &HFF0000

  Vert(2).Pos = vec3(0, 0.5, 0)
  Vert(2).Color = &HFF0000

  D3DVertexBuffer8SetData vBuffer, vSize * 3, vSize * 3, 0, Vert(0)
End Sub

Два треугольника, синий и красный, расположены в начале координат и взаимно пересекаются, создавая фигуру, наподобие наконечника стрелы, направленной вверх. Жмем <F5> и видим синий треугольник. Почему нет красного? Правильно, красный треугольник расположен к нам боком и его не видно. Чтобы его увидеть – переместим наблюдателя:
Code

D3DXMatrixLookAtLH Mtrx, vec3(1, 0, -2), vec3(0, 0, 0), vec3(0, 1, 0)

С этой позиции видно оба треугольника. Вернем наблюдателя на место и попробуем по-другому. Добавим перед вызовом Render такие строки:
Code

  D3DXMatrixRotationY Mtrx, Timer
  d3dDevice.SetTransform D3DTS_WORLD, Mtrx

Теперь, не меняя позиции наблюдателя, мы видим сцену с разных сторон. Мы вращаем саму сцену.
Вернемся к рассмотрению D3DTS_PROJECTION, почему бы не расширить зону видимости по оси Z от 0 до ∞? Дело в использовании ZBuffer, расстояние до рисуемого пикселя преобразуется перед записью в ZBuffer в значение от 0 до 1, что мы видели при использовании приведенного формата. Это значение имеет конечную точность, чем шире мы будем раздвигать границы, тем больше вероятность ошибки при Z-отсечении. Попробуем поменять значения zn и zf:
Code

D3DXMatrixPerspectiveFovLH Mtrx, 3.141593 / 4, Me.ScaleHeight / Me.ScaleWidth, 0.0001, 1000

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

Do While Running
  DoEvents
  D3DXMatrixRotationY Mtrx, Timer
  d3dDevice.SetTransform D3DTS_WORLD, Mtrx
  D3DXMatrixTranslation Mtrx, 0, 0, 1
  d3dDevice.MultiplyTransform D3DTS_WORLD, Mtrx
  Render
Loop

Здесь мы рассчитали матрицу вращения, записали ее в D3DTS_WORLD, потом рассчитали матрицу перемещения и домножили D3DTS_WORLD на эту матрицу.
Запускаем программу – результат не совсем правильный, «стрела» переместилась на некоторое расстояние, но продолжает вращаться вокруг начала координат, а не вокруг своей оси, которая с началом координат уже не совпадает. Дело в том, что в умножении матриц, в отличие от умножения чисел, важен порядок множителей, и этот порядок обратный по отношению к порядку трансформаций. Поменяем местами матрицы таким образом:
Code

D3DXMatrixTranslation Mtrx, 0, 0, 1
d3dDevice.SetTransform D3DTS_WORLD, Mtrx
D3DXMatrixRotationY Mtrx, Timer
d3dDevice.MultiplyTransform D3DTS_WORLD, Mtrx

Теперь цель достигнута, «стрела» отодвинулась на 1 вдоль оси Z и вращается вокруг своей оси. Того же результата можно достичь по-другому – сначала рассчитать матрицу необходимой трансформации, перемножив матрицы вращения и перемещения, а потом записать в D3DTS_WORLD полученную матрицу. Задайте еще одну матрицу:
Code

Dim Mtrx2 As D3DMATRIX

И поменяйте строки в основном цикле так:
Code

D3DXMatrixTranslation Mtrx, 0, 0, 1
D3DXMatrixRotationY Mtrx2, Timer
D3DXMatrixMultiply Mtrx, Mtrx2, Mtrx
d3dDevice.SetTransform D3DTS_WORLD, Mtrx

Как обычно, код данного проекта находится на компакт-диске в папке Pr04.

1.6. Рисуем цилиндр. TriangleStrip
От проекта к проекту мы постепенно осваиваем приемы программирования DirectX. Как правило, каждый новый проект основывается на предыдущих, поэтому дальше я не буду комментировать все изменения и дополнения, если эти изменения и дополнения уже изучены. Если, например, в новом проекте изменена позиция наблюдателя (матрица трансформации D3DTS_VIEW), то вы можете понять это и без комментариев, так как у вас есть код всех проектов.
Представим, что нам нужно изобразить цилиндр. Естественно мы его имитируем с помощью призмы, ведь нам недоступны кривые линии. Призма, имеющая 64 боковых грани уже достаточно близко соответствует цилиндру, остановимся на этом числе граней. Каждая грань призмы – это прямоугольник, который мы можем отобразить двумя треугольниками. Итого нам нужно 128 треугольников или 128 * 3 = 384 вертекса. Согласитесь, многовато. А ведь позиции каждого вертекса совпадают с позицией еще двух вертексов, принадлежащих другим треугольникам, если мы сможем вместо трех таких вертексов использовать один – мы уменьшим размер вертексного буфера в 3 раза.
И такой способ есть, он заключается в использовании параметра D3DPT_TRIANGLESTRIP вместо D3DPT_TRIANGLELIST в методе d3dDevice.DrawPrimitive. Рассмотрим принцип его работы. Первые три вертекса из вертексного буфера, как и раньше, соответствуют первому отображаемому треугольнику, а вот следующий треугольник задан не 4-ым, 5-ым, 6-ым, а 2-ым, 3-ым, 4-ым вертексами. Только лицевой уже считается та сторона, на которой вертексы расположены против часовой стрелки. Следующий треугольник задается 3-ым, 4-ым, 5-ым вертексами, на лицевой стороне вертексы опять расположены по часовой стрелке, и так далее. Это можно изобразить так:

В результате получается лента из треугольников, где для N треугольников необходимо задать N+2 вертексов. Если наш цилиндр задать такой лентой, свернутой в кольцо, нам понадобится всего 130 вертексов.
Если непосредственно задавать 130 вертексов, это займет много страниц кода. Поэтому переделаем процедуру InitGeometry. Создадим вспомогательную функцию для задания одного вертекса:

Code

Private Function Vertex(X As Single, Y As Single, Z As Single, C As Long) As vFormat
  Vertex.Pos = vec3(X, Y, Z)
  Vertex.Color = C
End Function

А InitGeometry будет выглядеть так:
Code

Private Sub InitGeometry()
Dim n As Long
  Set vBuffer = d3dDevice.CreateVertexBuffer(2 * 65 * vSize, 0, vFlag, D3DPOOL_DEFAULT)
  For n = 0 To 64
  Vert(0) = Vertex(Sin(2 * Pi * n / 64), -1, Cos(2 * Pi * n / 64), Rnd * &HFFFFFF)
  Vert(1) = Vertex(Sin(2 * Pi * n / 64), 1, Cos(2 * Pi * n / 64), Rnd * &HFFFFFF)
  D3DVertexBuffer8SetData vBuffer, vSize * 2 * n, vSize * 2, 0, Vert(0)
  Next n
End Sub

И заменим в процедуре Render параметр D3DPT_TRIANGLELIST на D3DPT_TRIANGLESTRIP:
Code

d3dDevice.DrawPrimitive D3DPT_TRIANGLESTRIP, 0, 128

Это означает вывод ленты из 128 треугольников из вертексного буфера, начиная с нулевого вертекса. Запускаем программу и видим разноцветный цилиндр.
Код данного проекта находится на компакт-диске в папке Pr05.

Продолжение

Категория: Программирование | Добавил: -Mikle- (14 Января 2011)
Просмотров: 16557 | Комментарии: 6 | Рейтинг: 4.8/6 |
Теги: dx8vb, DirectX 8.1, vb, программирование, DirectX 8, Direct3D, VB6, Visual Basic, Visual Basic 6, Программирование 3D графики
Дополнительные опции:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:

Игровые объявления и предложения:
Если вас заинтересовал материал «Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 1», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела. Предлагаются такие схожие материалы: Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.

Всего комментариев: 6
+0-
5 eXePir   (08 Декабря 2012 15:31) [Материал]
блин у меня что то не распознал vFormat пишет "User-Defined type not defined!" может потому что я скачал не directx sdk а просто библиотеку directx8 for visual basic type library

+0-
6 -Mikle-   (18 Декабря 2012 10:51) [Материал]
-Mikle-Это должно работать без SDK, попробуй готовый пример из архива, должен запуститься. Ищи ошибку.

+0-
4 FSO   (22 Июня 2012 17:21) [Материал]
FSOБлин, трудно разобраться, если ты в этом деле аматор. Подробностей не хватает.

+0-
3 FSO   (22 Июня 2012 11:52) [Материал]
FSOVisual Basic тот ещё монстр. Уважаю =)

+3-
2 -Mikle-   (15 Января 2011 16:59) [Материал]
-Mikle->язык перестал быть актуальным на новых платформах
Даже не язык, а порт DX8 к языку. Причём он убит искусственно, не успев стать неактуальным - Microsoft продвигает .NET.

+1-
1 GC-Vic   (15 Января 2011 16:41) [Материал]
GC-VicТо, что книга не вышла, а язык перестал быть актуальным на новых платформах – это ужасно. Но исследования, я думаю, не прошли зря. Всё движется, всё меняется…

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • LawMaker Game Engine
  • Verge
  • Demoniak3D
  • RPG Maker MV
  • DreamSDK
  • Pixel Game Maker MV
  • Raydium Engine
  • S2 Engine
  • J2DS
  • Kodu
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2022 Рейтинг