Visual Basic - мастерская разработчика
Библиотеки

DirectX

Обзоры
DirectDraw
Direct3D
DirectX Audio
DirectPlay
DirectInput
Fido Topics
SourceCode
Tools&Libs

OpenGL

Статьи и учебники
Fido Topics
SourceCode
Tools&Libs

Архив по Glide

Движки

Обзоры
Учебники
SourceCode
Downloads

Создание игр

Ваши игры

Обзорные статьи
Учебники
Fido Topics
SourceCode
Download

Stuff

Программер-Чат

Псевдо-FTP
Disclaimer
Оффтопик

 

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

VB Edition

Часть 4- Добавляем освещение

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

Приветствую вас в четвертой части учебника DirectX Graphics!
В этой части мы будем заниматься добавлением источника освещения в нашу сцену. Освещение в DirectX Graphics может быть различных типов: вещественный свет, направленный свет, точечный свет... Подробнее я на этом остановлюсь в какой-нибудь теоретической статье, а сеячас скажу только, что в этой части мы будем использовать направленный свет (Directional lighting). У направленного света источник находится в начале координат, и еще есть направление. Оно указывается конкретной 3D-координатой, программно - объектом D3DVECTOR. В эту точку свет и светит (уж простите за каламбур :) Свет так же имеет расстояние, на которое он распространяется.

 

Может вы помните игры, которые были давно и под ДОС? Там тоже было примитивнейшее освещение, рассчитываемое вручную. Так вот, теперь нам не надо этим заниматься! Рассчет освещения берет на себя сам Direct3D. Это хорошо, так как формулы освещения громоздки и у любого начинающего вызвали бы естесственное отторжение при первом же взгляде.

Вобщем, теоретически я вас загрузил, теперь переходим к практике.
Создайте новый проект, к нему подключите библиотеку dx8 for VB, нарисуйте форме caption и давайте писать программу:

'Объявляем объекты DirectX
Dim dx As New DirectX8 'Класс DirectX8
Dim d3d As Direct3D8 'Объект Direct3D
Dim d3dDevice As Direct3DDevice8 'Объект устройства рендеринга
Dim pVB As Direct3DVertexBuffer8 'Буфер для вершин.

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

'Структура, определяющая наш формат вершины (aka FVF)
Private Type CUSTOMVERTEX
Position As D3DVECTOR
Normal As D3DVECTOR
End Type

'Флаговое описание нашего FVF
Private Const D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ Or D3DFVF_NORMAL)

Private Const pi = 3.141592

 

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

Каждая поверхность в объекте имеет перпендикулярный поверхности вектор - нормаль. Направление перпендикуляра определяется порядком, в котором заданы вершины поверхности. Этот порядок определяет переднюю и заднюю стороны поверхности. Вы можете почитать об этом в статье "Основы трехмерной графики". Естественно, что нормаль выходит из передней стороны поверхности. Задняя сторона как правило не отрисовывается. Так вот, нормали выходящие из поверхностей называются поверхностными нормалями (ну или нормалями поверхностей). Вам не надо указывать их, так как система сама вычисляет их по мере необходимости. Существует так же другой тип нормалей - нормали вершин. Это нормаль, которая выходит из вершины.

Нормаль вершины используется при вычислении затенения примитива по методу Гуро, а так же при расчете освещения примитива и текстурных эффектов. Давайте разберемся в чем тут дело. На картинке вы можете видеть две поверхности повернутые к вам боком S1 и S2.

У них есть нормали поверхностей Ns1 и Ns2. Есть так же нормаль вершины Nv. Вы видите, что углы между нормалями поверхностей и нормалью вершины одинаковые. Это значит, что Direct3D при наложении световых эффектов будет брать этот угол и равномерно интерполировать цвет вершины как на поверхность S1, так и на поверхность S2. При этом получается плавный переход света через вершину из которой выходит нормаль.
На другой картинке, углы между нормалями не одинаковые. Это значит, что одна из сторон получит больше света и цвет вершины будет неравномерно интерполироваться на остальные пиксели поверхностей.

В кратце, вот для этого и нужны нормали. Ну как вам мои пара слов? :) Ладно, давайте писать программу дальше

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

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


'При выходе все уничтожить
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
Set pVB = 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

'Инициализация 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 = True
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC
d3dpp.BackBufferFormat = DispMode.Format
d3dpp.BackBufferCount = 1
d3dpp.EnableAutoDepthStencil = True
d3dpp.AutoDepthStencilFormat = D3DFMT_D16

Мы будем работать с буфером глубины, поэтому добавили к структуре d3dpp три строчки с определениями формата этого буфера.

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

'Убрать culling так, чтобы мы видели и переднюю и заднюю сторону треуголника
Call d3dDevice.SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE)

'Включить Z-буфер
Call d3dDevice.SetRenderState(D3DRS_ZENABLE, 1)

End Sub

Здесь отличие только в том, что в самом конце InitD3D мы включаем Z-буфер. Как вы наверное знаете, Z-буфер - это вспомогательная поверхности, в которой вместо информации о пикселях хранится информация о расстоянии точки от обозревателя. Это помогает правильно и быстро отображать перекрывающиеся геометрические фигуры.

Функция InitGeometry претерпела большие изменения:

Private Sub InitGeometry()
Dim Vertices(100) As CUSTOMVERTEX
Dim I As Integer, Theta As Single

Мы сразу создаем буфер под 100 вершин.

'Указать координаты вершин и нормали для цилиндра
For I = 0 To 49
Theta = (2 * pi * I) / (50 - 1)
Vertices(2 * I + 0).Position = vec3(Sin(Theta), -1, Cos(Theta))
Vertices(2 * I + 0).Normal = vec3(Sin(Theta), 0, Cos(Theta))
Vertices(2 * I + 1).Position = vec3(Sin(Theta), 1, Cos(Theta))
Vertices(2 * I + 1).Normal = vec3(Sin(Theta), 0, Cos(Theta))
Next I


'Создать буфер под хранение информации о вершинах
Set pVB = d3dDevice.CreateVertexBuffer(50 * 2 * Len(Vertices(0)), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT)
'Скопировать информацию о вершинах в буфер
Call D3DVertexBuffer8SetData(pVB, 0, Len(Vertices(0)) * 100, 0, Vertices(0))

End Sub

После этой процедуры, мы получим буфер вершин, задающих цилиндр. Теперь, задаем матрицы, чтобы видеть наш мир в движении:

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

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

Теперь мы используем функцию D3DXMatrixRotationAxis. Она выполняет преобразование (вращение) матрицы по указанным осям. В данном случае, мы указали все оси и угол Timer/4, чтобы получать равномерное вращение, привязанное по времени.

'Установить матрицу обзора
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 v As CUSTOMVERTEX
Dim sizeOfVertex As Long

DoEvents

sizeOfVertex = Len(v)

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

Теперь, мы очищаем так же и Z-буфер.

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

Установим освещение и материал объектов:

'Установить материал и свет
SetupLights

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

'Выполнить рендеринг треугольника по координатам, заданным в буфере вершин
Call d3dDevice.SetStreamSource(0, pVB, sizeOfVertex)
Call d3dDevice.SetVertexShader(D3DFVF_CUSTOMVERTEX)
Call d3dDevice.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, (4 * 25) - 2)

Обратите внимание, что теперь мы рендерим не набор треугольников (TRIANGLELIST), а ленту треугольников (TRIANGLESTRIP). Более подробно о лентах и веерах треугольников читайте в теоретической статье, которая должна быть где-то вокруг.

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

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

Вот она, функция SetupLights, которая осветит нам путь в темном месте :)

Private Sub SetupLights()
Dim mtrl As D3DMATERIAL8
Dim col As D3DCOLORVALUE
Dim Light As D3DLIGHT8

'Создать и назначить объекту материал желтого цвета
With col: .r = 1: .g = 1: .b = 0: .a = 1: End With
mtrl.diffuse = col
mtrl.Ambient = col
Call d3dDevice.SetMaterial(mtrl)

Как я уже объяснял, материал задает то, как свет будет отражаться от объекта. После вызова d3dDevice.SetMaterial каждый примитив будет рендериться с помощью указанного материала вплоть до следующего вызова этого метода.

'Установить белый направленный свет с осциллирующием направлением
Light.Type = D3DLIGHT_DIRECTIONAL
Light.diffuse.r = 1
Light.diffuse.g = 1
Light.diffuse.b = 1

Здесь мы задали параметры структуры D3DLIGHT8. Вы можете видеть, что мы задали направленный свет. Затем, мы задали рассеивающий цвет света.

Light.Direction.x = Cos(Timer * 2)
Light.Direction.y = 1
Light.Direction.z = Sin(Timer * 2)
Light.Range = 1000

Направление света будет врщаться у нас по кругу, для интереса :) Наконец, мы задаем дистанцию, на которой будет действовать свет.

Call d3dDevice.SetLight(0, Light)

Эта операция назначает свет устройству рендеринга. Первый нолик в параметрах означает индекс, под каким будет назначен свет. Если такому индексу уже был назначен свет, то он перезаписывается. В результате получается своеобразная база данных различных "светильников" :)

Call d3dDevice.LightEnable(0, True)

Теперь мы говорим устройству рендеринга, что свет под индексом 0 включен и его надо рендерить.

Call d3dDevice.SetRenderState(D3DRS_LIGHTING, True)

Сия строчка разрешает рендеринг света.

'Установить некоторый вещественный свет
Call d3dDevice.SetRenderState(D3DRS_AMBIENT, &H202020)

End Sub

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

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

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

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

[Вверх]

Posted: 23.01.2k1
Autor: Antiloop
<anti_loop@mail.ru>

 


Проект
Создание Народного Учебника по OpenGL

Участвовать!
Поиск
Найдите статью или файл:


Рассылка
Новости сайта
La Vision в вашем почтовом ящике








Программирование на С++ Delphi и Паскаль
Центр демо-искусства в России