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

код на языке vb
 
Private Type vFormat
  Pos As D3DVECTOR
  Color As Long
End Type
 

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

код на языке vb
 
Dim numIndex As Long, numTri As Long, numVertex As Long
 

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

код на языке vb
 
Dim iBuf As Direct3DIndexBuffer8
 

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

код на языке vb
 
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-го байта, эта часть выглядит так:

код на языке vb
 
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:

код на языке vb
 
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 появится новая строка:

код на языке vb
 
  d3dDevice.SetIndices iBuf, 0&
 

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

код на языке vb
 
  d3dDevice.DrawIndexedPrimitive D3DPT_TRIANGLELIST, 0&, numVertex, 0&, numTri
 

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