26 января 2010 в 13:44
Уроки Direct3D - 1.9. Нормали, свет
Представим, что все пространство пронизывает направленный свет. Каким образом можно определить, насколько ярко будет освещен произвольный участок поверхности? Очевидно, что если угол между направлением на источник света и вектором, перпендикулярным поверхности больше 90º, то свет на поверхность падать не будет вообще. Если этот угол меньше – свет на поверхность попадает, причем тем больше, чем меньше этот угол. Вектор, перпендикулярный поверхности, иначе называемый нормалью, является еще одним часто применяемым и очень полезным элементом формата вертекса. Найти нормаль не сложно, векторное произведение двух векторов обладает таким замечательным свойством, что результирующий вектор всегда перпендикулярен двум исходным векторам (либо равен нулю, если исходные вектора параллельны). Таким образом для нахождения нормали достаточно векторно перемножить два любых не параллельных вектора, лежащих на поверхности. И, как и с матрицами, нам самим не обязательно разбираться в дебрях векторной алгебры – в составе DirectX есть все необходимые готовые функции.
Векторами, лежащими на поверхности в районе вертекса (x, z) приблизительно можно считать вектор, соединяющий вертекс (x - 1, z) с вертексом (x + 1, z), и вектор, соединяющий вертекс (x, z - 1) с вертексом (x, z + 1). Назовем их, соответственно vX и vZ:
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
Здесь вектора перемножаются (D3DXVec3Cross) и результирующий вектор нормализуется, то есть приводится к единичной длине. В зависимости от величины компоненты x полученной нормали вычисляем цвет вертекса, как будто свет распространяется вдоль оси x:
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.
Добавим в формат вертекса новую компоненту – нормаль:
Private Type vFormat Pos As D3DVECTOR Normal As D3DVECTOR Color As Long End Type
Соответственно изменим флаговое описание:
Private Const vFlag = D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_DIFFUSE
В D3DInit разрешим использовать свет:
d3dDevice.SetRenderState D3DRS_LIGHTING, 1
Direct3D может обрабатывать до восьми источников света, их нумерация идет от нуля. Разрешим использование нулевого источника:
d3dDevice.LightEnable 0, 1
В функции Vertex будем принудительно окрашивать все вертексы в белый цвет:
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 переделаем фрагмент, в котором вертексы окрашивались в зависимости от нормали. Теперь мы просто записываем вычисленное значение нормали в вертекс:
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, в которой будем присваивать источнику света необходимые параметры:
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.
комментарии отсутствуют
авторизуйтесь