DirectX Graphics - начнем снова

VB Edition

Часть 6 - Загружаем объекты

[Предыдущая часть] [Следующая часть] [Приложения к этой части]

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

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

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

# Некий объект
идентификатор;
количество вершин;
Вершина_x_y_z;
# Конец объекта

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

Однако в этом учебнике мы не будет рассматривать создание собственного формата (по крайней мере пока), а вместо этого, мы будем учиться использовать уже существующий. Я говорю о формате для 3D-моделей, разработанном в Microsoft специально для использования в Direct3D. Я говорю о формате .X

Подробное описание формата вы найдете в SDK Documentations. Кстати, он довольно не плох и оптимально подходит для первоначальных целей обучения. В формате содержатся координаты вершин, информация о материалах, используемых текстурах и даже анимации. Создавать .x-файлы можно хоть в блокноте, но обычно это все же делают в каком-либо редакторе моеделей с экспортом в .3ds и последующей конвертацией в .x при помощи этой утилиты. Учебник по всему этому процессу выйдет на La Vision скорее всего отдельной статьей.

В этой части мы загрузим объект из уже подготовленного .x-файла, натянем на него текстуру и заставим вращаться. Заодно, кстати, сделаем так, чтобы наша программа работала в полноэкранном режиме. И объект и текстуру можно скачать тут вместе с готовым проектом.

Ну что ж, приступим. Создайте новый проект и добавьте в него необходимую ссылку на DirectX 8 Type Library...

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

Option Explicit

'Объявляем объекты DirectX
Dim dx As New DirectX8 'Класс DirectX8
Dim d3d As Direct3D8 'Объект Direct3D
Dim d3dx As New D3DX8 'Класс Direct3DX
Dim d3dDevice As Direct3DDevice8 'Объект устройства рендеринга

Dim Mesh As D3DXMesh 'Объект объекта, уж простите за тавтологию
Dim MeshMaterials() As D3DMATERIAL8 'Материалы для объекта
Dim MeshTextures() As Direct3DTexture8 'Текстуры для объекта
Dim NumMaterials As Long 'Количество материалов объекта

Ну с D3DXMesh все ясно, должен ведь загруженный объект где-то в памяти храниться. А вот следующие объявления требуют немного более подробных объяснений. D3DMATERIAL8 и Direct3DTexture8 - это указатели на хранилища для материалов и текстур, которые будет использовать загруженный объект. Дело в том, что D3DX сам позаботится о создании необходимых буферов вершин и даже распихает в них координаты из файла, но вот материалы и текстуры, объявленные в файле, остаются на нашей совести. И это даже хорошо, так как мы имеем возможность использовать несколько различных текстур и материалов для одного и того же объекта!

'Мы еще работаем?
Dim Running As Boolean

Private Const pi = 3.141592

'При нажатии на клавишу - конец программы
Private Sub Form_KeyPress(KeyAscii As Integer)
Running = False
End Sub

'При загрузке формы вызываем функцию инициализации Direct3D
Private Sub Form_Load()
Me.Show 'Показать окно
Running = True
InitD3D 'Инициализировать D3D
InitGeometry 'Создать буфер вершин
Do While Running 'Запустить цикл рендеринга
Render
Loop
End
End Sub

'Инициализация Direct3D
Private Sub InitD3D()
'Необходимые объекты и структуры
Dim DispMode As D3DDISPLAYMODE
Dim d3dpp As D3DPRESENT_PARAMETERS

'Создать объект Direct3D
Set d3d = dx.Direct3DCreate
'Получить текущий режим экрана
Call d3d.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, DispMode)

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

'Установить параметры создаваемого устройства рендеринга
d3dpp.Windowed = False
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC
d3dpp.BackBufferFormat = DispMode.Format
d3dpp.BackBufferWidth = 640
d3dpp.BackBufferHeight = 480
d3dpp.BackBufferCount = 1
d3dpp.EnableAutoDepthStencil = True
d3dpp.AutoDepthStencilFormat = D3DFMT_D16

Далее все стандартно. Обратите внимание, что мы используем Z-буфер и вещественный цвет.

'Создать устройство рендеринга
Set d3dDevice = d3d.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Me.hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, d3dpp)

'Включить вещественное освещение
Call d3dDevice.SetRenderState(D3DRS_AMBIENT, &HFFFFFFFF)

'Включить Z-буфер

Call d3dDevice.SetRenderState(D3DRS_ZENABLE, 1)

End Sub

Далее идет процедура, которая будет загружать объект и нужные ему текстуры. В это месте обычно появляется надпись "Loading..." :)

Private Sub InitGeometry()
Dim D3DXMtrlBuffer As D3DXBuffer 'Буфер материалов

Буфер материалов - это такая временная структура, куда загружается вся информация о материалах и текстурах прямо из файла. Далее вы и сами все поймете - читайте комментарии!

Dim TexName As String, I As Long

'Загружаем объект из указанного файла
Set Mesh = d3dx.LoadMeshFromX(App.Path & "\tiger.x", D3DXMESH_SYSTEMMEM, d3dDevice, _
Nothing, D3DXMtrlBuffer, NumMaterials)

If Mesh Is Nothing Then
MsgBox "Объект не загружен." & vbCrLf & "Проверьте правильность пути", vbCritical, "Ошибка"
Running = False
Exit Sub
End If

'Подготавливаем массивы для хранения материалов и текстур объекта
ReDim MeshMaterials(NumMaterials)
ReDim MeshTextures(NumMaterials)

'Теперь надо извлечь свойства материалов и структур, которые оказались
'в D3DXMtrlBuffer

For I = 0 To NumMaterials - 1
'Скопировать материал
d3dx.BufferGetMaterial D3DXMtrlBuffer, I, MeshMaterials(I)
'Установить вещественный цвет для материала, так как D3DX этого не делает
MeshMaterials(I).Ambient = MeshMaterials(I).diffuse
'Создать объект текстуры, основываясь на имени файла, указанном в объекте
TexName = d3dx.BufferGetTextureName(D3DXMtrlBuffer, I)
If TexName <> "" Then
Set MeshTextures(I) = d3dx.CreateTextureFromFile(d3dDevice, App.Path & "\" & TexName)
End If
Next I

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

'Освободить буфер материалов
Set D3DXMtrlBuffer = Nothing
End Sub

'При выходе все уничтожить
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
ReDim MeshTextures(0)
ReDim MeshMaterials(0)

Set Mesh = Nothing
Set d3dDevice = Nothing
Set d3d = Nothing
End Sub

'Вспомогательная функция для быстрого создания векторов
Function vec3(x As Single, y As Single, z As Single) As D3DVECTOR
vec3.x = x
vec3.y = y
vec3.z = z
End Function

В процедуре задания матриц, зададим вращение всго мира вокруг своей оси.

Private Sub SetupMatrices()
Dim MatWorld As D3DMATRIX
Dim MatView As D3DMATRIX
Dim MatProjection As D3DMATRIX

'Матрице мира задаем вращение объекта вокруг оси Y
Call D3DXMatrixRotationAxis(MatWorld, vec3(0, 1, 0), Timer / 4)
Call d3dDevice.SetTransform(D3DTS_WORLD, MatWorld)

'Установить матрицу обзора
Call D3DXMatrixLookAtLH(MatView, vec3(0, 3, -5), vec3(0, 0, 0), vec3(0, 1, 0))
Call d3dDevice.SetTransform(D3DTS_VIEW, MatView)

'Установить матрицу проекции
Call D3DXMatrixPerspectiveFovLH(MatProjection, pi / 4, 1, 1, 1000)
Call d3dDevice.SetTransform(D3DTS_PROJECTION, MatProjection)
End Sub

Наконец, последняя процедура - рендеринг.

Private Sub Render()
Dim I As Long

DoEvents

If d3dDevice Is Nothing Then Exit Sub

'Очистить задний буфер и z-буфер в синий цвет
Call d3dDevice.Clear(0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &HFF, 1, 0) 'Очистить вьюпорт

'Установить матрицы мира, обзора и проекции
SetupMatrices

Call d3dDevice.BeginScene 'Начало рендеринга

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

'Объеты разделены на части по признаку материала. Таким образом
'становится очень просто отрендерить все части по очереди

For I = 0 To NumMaterials - 1
'Установить материал и текстуру для этой части
d3dDevice.SetMaterial MeshMaterials(I)
d3dDevice.SetTexture 0, MeshTextures(I)

'Рисовать часть
Mesh.DrawSubset (I)
Next I

Call d3dDevice.EndScene 'Конец рендеринга

'Показать задний буфер человечеству (aka Flip)
Call d3dDevice.Present(ByVal 0, ByVal 0, 0, ByVal 0)
End Sub

Все! Компилируем и смотрим на тигренка. Когда надоест жмем ESC.

Приложение: Готовый проект (48k)

Приятного программирования: Anti

[Вверх]