Пятница, 19 Апреля 2024, 19:09

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

Меню сайта
Категории каталога
Создание игр [355]
Статьи об общих понятиях связанных с созданием игр.
Программирование [82]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [145]
Статьи о программах для создания игр, уроки и описания.
Софт [43]
Различные программы, в том числе в помощь игроделам.
2D-графика [14]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [16]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [5]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [161]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [129]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Типа какой из этих игр вы предпочитаете делать игры?
Всего ответов: 16791
Главная » Статьи » Программирование

Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 2
1.7. Текстурирование
Представьте, что нам нужно изобразить кирпичную стену. До сих пор мы рисовали цветные треугольники, очевидно, что при таком подходе для изображения стены понадобится огромное количество треугольников. Тут нам на помощь приходит текстурирование.
Определение:
Текстурирование – это использование плоского изображения (картинки) для закраски треугольника.

Строго говоря, текстура не всегда является плоской картинкой, бывают одно- и трехмерные текстуры. Но пока не будем усложнять, подавляющее число текстур, используемых в 3D-программировании – это плоские картинки.
Возьмем наш старый проект c треугольником (Pr02). Поменяем формат вертекса таким образом:

Code
Private Type vFormat
  PosX As Single
  PosY As Single
  PosZ As Single
  RHW As Single
  tu As Single
  tv As Single
End Type

И флаговое описание вертекса:

Code
Private Const vFlag = D3DFVF_XYZRHW Or D3DFVF_TEX1

В формате вертекса исчез цвет, но появились два новых поля – tu и tv, это координаты на текстуре (отсчет идет от верхнего левого угла), отмечающие ту точку текстуры, которая будет отображаться на наш вертекс. Еще раз осмыслите разницу – это координаты не в пространстве, а на текстуре.
Для текстур в Direct3D существует специальный класс Direct3DTexture8.
Создадим переменную для экземпляра этого класса:

Code
Dim Tex As Direct3DTexture8

Не забудьте внести соответствующую строку в ClearAll! Так же добавьте инициализацию уже знакомого нам D3DX, который поможет нам загрузить текстуру из файла:

Code
Set Tex = d3dx.CreateTextureFromFile(d3dDevice, App.Path & "\brick.jpg")

В InitGeometry уберем строки, описывающие цвет вертексов и определим текстурные координаты:

Code
Vert(0).tu = 0
  Vert(0).tv = 0
  Vert(1).tu = 1
  Vert(1).tv = 0
  Vert(2).tu = 0
  Vert(2).tv = 1

Остается в Render добавить строку, указывающую нашему устройству рендера использовать именно эту текстуру:

Code
d3dDevice.SetTexture 0, Tex

Файл brick.jpg можно взять с компакт-диска и поместить в папку с проектом. Запускаем проект и видим «кирпичный» треугольник. Текстурные координаты «0, 0» соответствуют верхнему левому, «1, 0» – верхнему правому, а «0, 1» – нижнему левому углам. Поменяйте текстурные координаты:

Code
Vert(0).tu = -1
  Vert(0).tv = -1
  Vert(1).tu = 1.5
  Vert(1).tv = -1
  Vert(2).tu = -1
  Vert(2).tv = 1.5

Этот опыт демонстрирует, что в качестве текстурных координат можно применять числа, выходящие из диапазона 0 .. 1, при этом текстура повторяется.
Если затекстурить несколько треугольников с общими вертексами, равномерно распределив по вертексам текстурные координаты, стыки между треугольниками будут незаметны. Возьмем цилиндр из нашего предыдущего проекта и внесем подобные изменения. Так же, как в проекте с треугольником, уберем цвет и добавим текстурные координаты в формат вертекса, изменим флаговое описание, добавим инициализацию и уничтожение текстуры. Стоит остановиться на инициализации геометрии. Функция Vertex теперь приобретет такой вид:

Code
Private Function Vertex(X As Single, Y As Single, Z As Single, _
Tu As Single, Tv As Single) As vFormat
  Vertex.Pos = vec3(X, Y, Z)
  Vertex.Tu = Tu
  Vertex.Tv = Tv
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), 6 * n / 64, 2)
  Vert(1) = Vertex(Sin(2 * Pi * n / 64), 1, Cos(2 * Pi * n / 64), 6 * n / 64, 0)
  D3DVertexBuffer8SetData vBuffer, vSize * 2 * n, vSize * 2, 0, Vert(0)
  Next n
End Sub

Текстурная координата Tv распределена пропорционально высоте цилиндра (координата Y), а Tu пропорционально углу относительно центральной оси. Посмотрите на результат и, ради тренировки, попробуйте покрыть цилиндр этой же текстурой под углом 45? таким образом, чтобы не осталось шва.
На компакт-диске в папках Pr06 и Pr07 находятся исходные коды проектов с затекстуренными треугольником и цилиндром.

1.8. Индексирование вертексов
Представьте, что у нас есть карта некоторой поверхности, представляющая из себя список высот точек на прямоугольном участке. Точки расположены равномерно через равные интервалы, при таком расположении нам достаточно указать только координату Y (высота) точки, а другие координаты легко вычисляются из порядкового номера точки. Такая карта высот называется регулярной сеткой.
Для простоты возьмем квадратную сетку размером 64 * 64. Часто для хранения таких карт применяют монохромные картинки, где яркость соответствующего пикселя интерпретируется как высота. В папке Pr08 будет наш новый проект, там можно взять соответствующую карту – файл HeightMap.tga.
Сетка такого размера будет состоять из 63 * 63 квадратных ячеек, каждую из которых можно изобразить двумя треугольниками. То есть если использовать обычный TRIANGLELIST, то нам понадобится 63 * 63 * 2 * 3 = 23814 вертексов. Можно разрезать карту на 63 полосы TRIANGLESTRIP, тогда число вертексов уменьшится до 64 * 63 * 2 = 8064. Неплохая экономия, но ведь реально на сетке 64 * 64 = 4096 точки, можно ли обойтись таким же количеством вертексов? Да, можно, для этого создают не один, а два буфера, первый – вертексный, содержащий только необходимые вертексы без повторов. Второй буфер будет содержать индексы, то есть порядковые номера вертексов в вертексном буфере. Индексы расположены так, что вместо дублирования вертекса, мы дублируем индекс – указатель на вертекс. Представим, что вертексы пронумерованы рядами слева направо, от ближних к дальним, нумерация идет от нуля. Для вывода с использованием TRIANGLELIST можно расположить индексы так:

Первый треугольник образован вертексами 0, 64, 65, то есть такими и будут первые три индекса – 0, 64, 65. Второй – 0, 65, 1 и так далее. Карта, описанная таким образом, будет содержать 4096 вертексов и 23814 индексов. Что же мы выиграли? Дело в том, что индекс – это, в отличие от вертекса, обычное 16-ти либо 32-х битное число, которое занимает значительно меньше памяти, чем вертекс, особенно при сложных форматах вертекса. Кроме того, при расчетах трансформаций процессор вынужден повторно производить вычисления для продублированных вертексов, так как считает их разными, а при индексировании один раз рассчитанный вертекс попадает в кэш, и при повторном обращении уже может не рассчитываться. Использование индексов дает и другие преимущества, которые мы рассмотрим позже.
Создадим новый проект с уже знакомой нам инициализацией d3dDevice и вертексным буфером с таким форматом:

Code
Private Type vFormat
  Pos As D3DVECTOR
  Color As Long
End Type

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

Code
Dim numIndex As Long, numTri As Long, numVertex As Long

И одну переменную нового типа Direct3DIndexBuffer8:

Code
Dim iBuf As Direct3DIndexBuffer8

Как и всякую переменную объектного типа добавьте iBuf в список для уничтожения. В соответствие с форматом вертекса функция Vertex приобретет такой вид:

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 будет состоять из двух частей, в первой инициализируется вертексный буфер. Интересующие нас данные расположены в TGA файле с 19-го байта, эта часть выглядит так:

Code
Dim x As Long, z As Long, nf As Integer, b As Byte
Dim Vert(64 * 64 - 1) As vFormat
  vSize = Len(Vert(0))
  numVertex = 64 * 64
  Set vBuf = d3dDevice.CreateVertexBuffer(numVertex * vSize, 0, vFlag, D3DPOOL_DEFAULT)
  nf = FreeFile
  Open "HeightMap.tga" For Binary As #nf
  For z = 0 To 63
  For x = 0 To 63
  Get #nf, x + z * 64 + 19, b
  Vert(x + z * 64) = Vertex(x - 31.5, b * 0.05, z - 31.5, &H808080)
  Next x
  Next z
  Close #nf

При вызове функции Vertex координаты x и z получены вычитанием 31.5 из соответствующих координат карты, это сделано с целью центровки карты относительно начала координат. Для получения координаты y данные о высоте, взятые из файла, умножаются на 0.05 – это вертикальный масштаб. Для всех вертексов задан серый цвет &H808080.
Новая для нас вторая часть процедуры InitGeometry:

Code
Dim Ind(63 * 63 * 2 * 3 - 1) As Integer
  numTri = 63 * 63 * 2
  numIndex = numTri * 3
  Set iBuf = d3dDevice.CreateIndexBuffer(numIndex * 2, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT)
  For z = 0 To 62
  For x = 0 To 62
  Ind((z * 63 + x) * 2 * 3 + 0) = (z + 0) * 64 + x + 0
  Ind((z * 63 + x) * 2 * 3 + 1) = (z + 1) * 64 + x + 0
  Ind((z * 63 + x) * 2 * 3 + 2) = (z + 1) * 64 + x + 1
  Ind((z * 63 + x) * 2 * 3 + 3) = (z + 0) * 64 + x + 0
  Ind((z * 63 + x) * 2 * 3 + 4) = (z + 1) * 64 + x + 1
  Ind((z * 63 + x) * 2 * 3 + 5) = (z + 0) * 64 + x + 1
  Next x
  Next z
  D3DIndexBuffer8SetData iBuf, 0, numIndex * 2, 0, Ind(0)

Двойным циклом проходим по всем ячейкам карты и шестью индексами задаем два треугольника в соответствие с рисунком в начале главы.
В процедуре Render появится новая строка:

Code
d3dDevice.SetIndices iBuf, 0

Здесь мы указываем устройству рендера какой индексный буфер использовать и с какого индекса начинать выборку. И осталось изменить само рисование:

Code
d3dDevice.DrawIndexedPrimitive D3DPT_TRIANGLELIST, 0, numVertex, 0, numTri

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

1.9. Нормали, свет
Представим, что все пространство пронизывает направленный свет. Каким образом можно определить, насколько ярко будет освещен произвольный участок поверхности? Очевидно, что если угол между направлением на источник света и вектором, перпендикулярным поверхности больше 90º, то свет на поверхность падать не будет вообще. Если этот угол меньше – свет на поверхность попадает, причем тем больше, чем меньше этот угол. Вектор, перпендикулярный поверхности, иначе называемый нормалью, является еще одним часто применяемым и очень полезным элементом формата вертекса. Найти нормаль не сложно, векторное произведение двух векторов обладает таким замечательным свойством, что результирующий вектор всегда перпендикулярен двум исходным векторам (либо равен нулю, если исходные вектора параллельны). Таким образом для нахождения нормали достаточно векторно перемножить два любых не параллельных вектора, лежащих на поверхности. И, как и с матрицами, нам самим не обязательно разбираться в дебрях векторной алгебры – в составе DirectX есть все необходимые готовые функции.
Векторами, лежащими на поверхности в районе вертекса (x, z) приблизительно можно считать вектор, соединяющий вертекс (x - 1, z) с вертексом (x + 1, z), и вектор, соединяющий вертекс (x, z - 1) с вертексом (x, z + 1). Назовем их, соответственно vX и vZ:

Code
vX = vec3(2, Vert(((x - 1) And 63) + z * 64).Pos.y - Vert(((x + 1) And 63) + _
  z * 64).Pos.y, 0)
  vZ = vec3(0, Vert(x + ((z - 1) And 63) * 64).Pos.y - Vert((x + ((z + 1) And 63) * _
  64)).Pos.y, 2)

Далее две строки:

Code
D3DXVec3Cross v, vX, vZ
  D3DXVec3Normalize v, v

Здесь вектора перемножаются (D3DXVec3Cross) и результирующий вектор нормализуется, то есть приводится к единичной длине. В зависимости от величины компоненты x полученной нормали вычисляем цвет вертекса, как будто свет распространяется вдоль оси x:

Code
If v.x > 0 Then
  Vert(x + z * 64).Color = Int(v.x * 255) * &H10101
  Else
  Vert(x + z * 64).Color = 0
  End If

Теперь наш фрагмент поверхности выглядит значительно реалистичнее. Результат можно найти в папке Pr08.
Продолжаем. Свет смотрится достаточно натурально, но что, если нам нужно менять направление света? Каждый раз рассчитывать цвет всех вертексов? Такой подход явно не годится, эту работу можно переложить на DirectX.
Добавим в формат вертекса новую компоненту – нормаль:

Code
Private Type vFormat
  Pos As D3DVECTOR
  Normal As D3DVECTOR
  Color As Long
End Type

Соответственно изменим флаговое описание:

Code
Private Const vFlag = D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_DIFFUSE

В D3DInit разрешим использовать свет:

Code
d3dDevice.SetRenderState D3DRS_LIGHTING, 1

Direct3D может обрабатывать до восьми источников света, их нумерация идет от нуля. Разрешим использование нулевого источника:

Code
d3dDevice.LightEnable 0, 1

В функции Vertex будем принудительно окрашивать все вертексы в белый цвет:

Code
Private Function Vertex(x As Single, y As Single, z As Single) As vFormat
  Vertex.Pos = vec3(x, y, z)
  Vertex.Color = &HFFFFFF
End Function

В процедуре InitGeometry переделаем фрагмент, в котором вертексы окрашивались в зависимости от нормали. Теперь мы просто записываем вычисленное значение нормали в вертекс:

Code
For z = 0 To 63
  For x = 0 To 63
  vX = vec3(2, Vert(((x - 1) And 63) + z * 64).Pos.y - Vert(((x + 1) And 63) + z * 64).Pos.y, 0)
  vZ = vec3(0, Vert(x + ((z - 1) And 63) * 64).Pos.y - Vert((x + ((z + 1) And 63) * 64)).Pos.y, 2)
  D3DXVec3Cross v, vX, vZ
  D3DXVec3Normalize v, v
  Vert(x + z * 64).Normal = v
  Next x
  Next z

И создадим новую процедуру InitLight, в которой будем присваивать источнику света необходимые параметры:

Code
Private Sub InitLight()
Dim Light As D3DLIGHT8
  Light.Type = D3DLIGHT_DIRECTIONAL
  Light.Direction = vec3(Sin(Timer), 0.6, Cos(Timer))
  Light.diffuse.r = 1
  Light.diffuse.g = 1
  Light.diffuse.b = 1
  d3dDevice.SetLight 0, Light
End Sub

Здесь создается переменная Light структурного типа D3DLIGHT8. В переменную записываем тип источника света – D3DLIGHT_DIRECTIONAL, то есть направленный свет. Раз направленный – зададим направление, в поле Direction записываем вектор, который будет менять направление с течением времени. Далее идет цвет, это все то же RGB, но задающиеся не тремя байтами, а Single значениями в диапазоне от 0 до 1. Цвет называется diffuse потому, что в дальнейшем он будет умножаться именно на diffuse компоненту в вертексе. Даем полный свет. И строкой d3dDevice.SetLight 0, Light «загоняем» наши параметры в нулевой источник света.
Вызов процедуры InitLight необходимо поместить внутрь нашего главного цикла в Form_Load, ведь направление света будет изменяться во времени, однократного вызова InitLight недостаточно.
Запускаем – если ошибок не было, то видим свет DirectX в действии, ну а если ошибки преследуют, то проект можно извлечь из папки Pr09 на компакт-диске.

1.10. Материал
Обратили внимание на то, что поле Color в формате вертекса стало явно избыточным? Если во все вертексы все равно пишется одно и то же значение цвета, то может можно его туда не писать вообще, а задать цвет как-то по другому? DirectX предоставляет такую возможность – это использование материала.
Уберем из формата вертекса поле Color, а также все, что с ним в программе было связано. Не буду уточнять, мы уже меняли формат вертекса неоднократно. Добавим в проект новую процедуру InitMaterial:

Code
Private Sub InitMaterial()
Dim Mat As D3DMATERIAL8
  Mat.diffuse.r = 1
  Mat.diffuse.g = 1
  Mat.diffuse.b = 1
  d3dDevice.SetMaterial Mat
End Sub

Переменная структурного типа D3DMATERIAL8 заполняется параметрами используемого материала и с помощью d3dDevice.SetMaterial мы даем указание этот материал использовать. Параметры материала неизменны, поэтому достаточно вызвать InitMaterial один раз. Поместим вызов перед главным циклом. Остановим для разнообразия вращение света и заставим вращаться саму карту. Тогда и вызов InitLight можно переместить из главного цикла в начало. Программа готова – можно запускать.
Использование материала вместо цвета в вертексе с одной стороны немного ограничило наши возможности – теперь мы не можем задать каждому вертексу свой цвет, с другой стороны у нас появились и новые возможности. Добавим к инициализации материала строку:

Code
Mat.Ambient = Mat.diffuse

Ambient, как и diffuse – это тоже цвет, но не направленный, а рассеянный. Заполнив поле Ambient у источника света, мы заставим равномерно светиться даже затененные участки. Можно не трогать источник света, а задать общий рассеянный свет. Добавьте в D3DInit еще одну строку:

Code
d3dDevice.SetRenderState D3DRS_AMBIENT, &H302080

Теперь при запуске мы видим, что затененные участки карты не черные, а темно синие. Довольно похоже на снег.
Пример использования материала находится в папке Pr10. Попробуйте, изменяя цвет света и материала, добиться различных оттенков, чтобы почувствовать работу света.
Ну и проведем еще один эксперимент на нашей карте – покроем ее текстурой. Добавьте в вертекс текстурные координаты:

Code
Private Type vFormat
  Pos As D3DVECTOR
  Normal As D3DVECTOR
  tu As Single
  tv As Single
End Type

Так же добавьте все, что необходимо для создания, использования и удаления текстуры. Текстуру травы можно взять в папке Pr11. Текстурные координаты поставим в зависимость от координат x и z:

Code
Vert(x + z * 64) = Vertex(x - 31.5, b * 0.03, z - 31.5, x * 0.02, z * 0.02)

Последние два параметра в функции Vertex – это и есть текстурные координаты.
Мы получили изображение поверхности земли, покрытой травой. Формат вертекса, используемый в этом проекте, является одним из наиболее часто применяемых в Direct3D. Соответствующее ему флаговое описание – комбинация трех констант D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_TEX1 даже имеет отдельное обозначение – константа D3DFVF_VERTEX.

Продолжение

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

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

Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • GLScene
  • Аперо
  • Rootex
  • FOnline
  • Game Hammer 2
  • Emcore3D
  • GemRB
  • Harfang 3D
  • Fighter Creator
  • Android FPS Maker
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2024 Рейтинг