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

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

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

код на языке vb
 
      D3DXVec3Cross v, vX, vZ
      D3DXVec3Normalize v, v
 

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

код на языке vb
 
      If v.x > 0 Then
        Vert(x + z * 64).Color = Int(v.x * 255) * &H10101
      Else
        Vert(x + z * 64).Color = 0
      End If
 

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

Добавим в формат вертекса новую компоненту – нормаль:

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

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

код на языке vb
 
Private Const vFlag = D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_DIFFUSE
 

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

код на языке vb
 
  d3dDevice.SetRenderState D3DRS_LIGHTING, 1
 

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

код на языке vb
 
  d3dDevice.LightEnable 0, 1

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

код на языке vb
 
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 переделаем фрагмент, в котором вертексы окрашивались в зависимости от нормали. Теперь мы просто записываем вычисленное значение нормали в вертекс:

код на языке vb
 
  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, в которой будем присваивать источнику света необходимые параметры:

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