Введение в программирование трехмерных игр с DX9

         

Буферы вершин и индексов


Буферы вершин и индексов обладают сходными интерфейсами и предоставляют одинаковые методы, так что мы будем их рассматривать вместе. Буфер вершин представляет собой непрерывный участок памяти в котором хранятся данные вершин. Аналогичным образом, буфер индексов— это непрерывный участок памяти в котором хранятся данные индексов. Мы используем буферы вершин и индексов для хранения данных из соответствующих массивов по той причине, что эти буферы могут располагаться в памяти видеокарты. Визуализация данных, находящихся в памяти видеокарты, выполняется гораздо быстрее, чем визуализация данных, расположенных в системной памяти.

В коде буфер вершин представляется интерфейсом IDirect3DVertexBuffer9, а буфер индексов представляется интерфейсом IDirect3DIndexBuffer9.



Дополнительные примеры


В сопроводительные файлы к этой главе включены три дополнительных примера. Для создания образующих сцену трехмерных объектов в них используются функции D3DXCreate*. Функции D3DXCreate* создают данные вершин в формате D3DFVF_XYZ| D3DFVF_NORMAL. Кроме того, эти функции за нас вычисляют нормали вершин для каждой сетки. Дополнительные примеры демонстрируют как использовать направленный, точечный и зональный свет. На Рисунок  5.8 показано окно программы, демонстрирующей использование направленного света.



Доступ к памяти буфера



3.1.2. Доступ к памяти буфера

Для доступа к памяти буфера вершин или индексов нам необходимо получить указатель на область памяти с содержимым буфера. Мы получаем указатель на содержимое с помощью метода Lock. Не забудьте после завершения работы с буфером разблокировать его. Получив указатель на область памяти, можно считывать и записывать информацию.

ПРИМЕЧАНИЕ

Если при создании буфера вершин или индексов был указан флаг D3DUSAGE_WRITEONLY, вы не сможете читать информацию из этого буфера. Попытка выполнить чтение приведет к возникновению ошибки.

HRESULT IDirect3DVertexBuffer9::Lock( UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags ); HRESULT IDirect3DIndexBuffer9::Lock( UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags );



Геометрические объекты DX


Создание трехмерных объектов путем прописывания в коде данных каждого, составляющего их треугольника, является утомительным занятием. К счастью, библиотека D3DX предлагает несколько методов, генерирующих данные сеток простых трехмерных объектов за нас.



Библиотека D3DX предоставляет следующие шесть методов для создания сеток:

D3DXCreateBox

D3DXCreateSphere

D3DXCreateCylinder

D3DXCreateTeapot

D3DXCreatePolygon

D3DXCreateTorus



Освещение


Чтобы добавить нашим сценам реализма необходимо освещение. Кроме того, освещение позволяет подчеркивать форму и объем объектов. Если мы используем освещение, нам больше не надо самостоятельно задавать цвета вершин; Direct3D обработает каждую вершину в своем механизме расчета освещенности и вычислит ее цвет основываясь на данных об источниках света, материале и ориентации поверхности относительно источников света. Вычисление цвета вершины на основе освещения модели дает более естественные результаты.

Цели

Изучить источники освещения, поддерживаемые Direct3D, и типы освещения, которое эти источники могут давать.

Разобраться, как задать взаимодействие света с поверхностью на которую он падает.

Узнать, как математически задается ориентация треугольной грани, чтобы вычислить под каким углом на нее падает свет.



Рисование в Direct


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

Цели

Узнать, как в Direct3D хранятся данные вершин и индексов.

Изучить, как можно менять способ отображения фигур с помощью режимов визуализации.

Научиться визуализировать сцены.

Узнать, как с помощью функций D3DXCreate* можно создавать сложные трехмерные объекты.



IDirectDevice:DrawIndexedPrimitive



3.4.2. IDirect3DDevice9::DrawIndexedPrimitive

Этот метод используется для рисования примитивов, использующих индексы.

HRESULT IDirect3DDevice9::DrawIndexedPrimitive( D3DPRIMITIVETYPE Type, INT BaseVertexIndex, UINT MinIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount );

Type — Тип рисуемого примитива. Помимо треугольников вы можете рисовать линии и точки. Поскольку мы используем треугольники, в данном параметре следует указать D3DPT_TRIANGLELIST.

BaseVertexIndex — Базисная величина, которая будет прибавлена к используемым в данном вызове индексам. Подробнее о ней говорится в находящемся после списка примечании.

MinIndex — Минимальное значение индекса, которое будет использовано.

NumVertices — Количество вершин, которые будут обработаны данным вызовом.

StartIndex — Номер элемента буфера индексов, который будет отмечен как стартовая точка с которой начнется чтение индексов.

PrimitiveCount — Количество рисуемых примитивов.

Пример использования метода:

_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12);

ПРИМЕЧАНИЕ

Параметр BaseVertexIndex заслуживает более подробного исследования. В исследовании вам поможет Рисунок  3.2. Локальные буферы индексов ссылаются на вершины в соответствующих локальных буферах вершин. Теперь представьте, что мы объединили вершины сферы, куба и цилиндра в одном общем глобальном буфере вершин. Теперь мы должны пересоздать для каждого объекта буфер индексов, чтобы индексы корректно указывали на вершины в новом общем буфере вершин. Новые индексы вычисляются путем сложения старого индекса со значением смещения, указывающего с какой позиции в общем буфере вершин начинаются данные вершин объекта. Обратите внимание, что смещение измеряется в вершинах, а не в байтах. Direct3D позволяет передать значение смещения в параметре BaseVertexIndex, вместо того чтобы самостоятельно пересчитывать индексы в зависимости от того, в каком месте общего буфера вершин находится объект. Перерасчет индексов в этом случае Direct3D выполнит самостоятельно.



IDirectDevice:DrawPrimitive



3.4.1. IDirect3DDevice9::DrawPrimitive

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

HRESULT IDirect3DDevice9::DrawPrimitive( D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount );

PrimitiveType— Тип рисуемого примитива. Помимо треугольников вы можете рисовать линии и точки. Поскольку мы используем треугольники, в данном параметре следует указать D3DPT_TRIANGLELIST.

StartVertex — Индекс элемента потока вершин с которого следует начать чтение данных вершин. Благодаря этому параметру мы можем рисовать только часть буфера вершин.

PrimitiveCount — Количество рисуемых примитивов.

А вот и пример использования метода:

// рисуем четыре треугольника _device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);



Источники света


Direct3D поддерживает источники света трех типов.

Точечный свет (point light)— У этого источника света есть местоположение в пространстве и он испускает свет во всех направлениях.



Лицевая нормаль поверхности



Рисунок 5.1. Лицевая нормаль поверхности



Direct3D поддерживает три модели источников


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

Компоненты света

В модели освещения Direct3D свет, испускаемый источниками, состоит из трех составляющих, или трех типов света:

Фоновый свет (ambient light) — Этот тип освещения моделирует свет, который отражается от других поверхностей и освещает всю сцену. Например, в сцене часто освещены те части объектов, которые не находятся в прямой видимости источника света. Эти части освещаются тем светом,который отражается от других поверхностей. Фоновый свет — это трюк, который используется для приблизительного моделирования этого отраженного света.

Рассеиваемый свет (diffuse light) — Этот свет распространяется в заданном направлении. Сталкиваясь с поверхностью он отражается равномерно во всех направлениях. Поэтому интенсивность достигшего глаза зрителя света не зависит от точки, с которой просматривается сцена, и местоположение зрителя можно не учитывать. Следовательно, при вычислении расеянного освещения надо учитывать только направление световых лучей и позицию поверхности. Это основная составляющая испускаемого источником света.

Отражаемый свет (specular light) — Этот свет распространяется в заданном направлении. Сталкиваясь с поверхностью он отражается строго в одном направлении, формируя блики, которые видимы только при взгляде на объект под определенным углом. Поскольку свет отражается только в одном направлении, ясно что при расчете отражаемого света необходимо принимать во внимание местоположение и ориентацию камеры, направление световых лучей и ориентацию поверхности. Отражаемый свет используется для моделирования света, формируемого освещенными объектами, такого как блики, появляющиеся при освещении полированных поверхностей.

Отражаемый свет требует гораздо большего объема вычислений, чем другие составляющие освещения; поэтому Direct3D позволяет отключать его. Фактически, по умолчанию эта составляющая не используется; чтобы включить ее, необходимо установить режим визуализации D3DRS_SPECULARENABLE.

Device->SetRenderState(D3DRS_SPECULARENABLE, true);

Каждая составляющая освещения представляется структурой D3DCOLORVALUE или D3DXCOLOR, которая определяет цвет. Вот несколько примеров разноцветных источников света:

D3DXCOLOR redAmbient(1.0f, 0.0f, 0.0f, 1.0f); D3DXCOLOR blueDiffuse(0.0f, 0.0f, 1.0f, 1.0f); D3DXCOLOR whiteSpecular(1.0f, 1.0f, 1.0f, 1.0f);

ПРИМЕЧАНИЕ

Когда структура D3DXCOLOR используется для описания источника света, альфа-компонента игнорируется.

Нормали вершин (vertex normals) основаны на той же самой идее, но в этом случае задается не нормаль для всего многоугольника, а отдельная нормаль для каждой образующей его вершины (Рисунок  5.2).

В структуре данных вершины нет членов данных для задания свойств материала; вместо этого нам надо задать используемый материал с помощью метода IDirect3DDevice9::SetMaterial(CONST D3DMATERIAL9* pMaterial).

Если мы хотим визуализировать несколько объектов, используя различные материалы, нам надо написать следующее:

D3DMATERIAL9 blueMaterial, redMaterial;

...// инициализация структур материалов

Device->SetMaterial(&blueMaterial); drawSphere(); // синяя сфера

Device->SetMaterial(&redMaterial); drawSphere(); // красная сфера


Начало и завершение сцены



3.4.3. Начало и завершение сцены

И последний фрагмент информации: помните, что все вызовы методов рисования должны находиться внутри пары вызовов IDirect3DDevice9::BeginScene и IDirect3DDevice9::EndScene. К примеру, следует писать:

_device->BeginScene(); _device->DrawPrimitive(...); _device->EndScene();



Направленный свет



Рисунок 5.5. Направленный свет



Материалы


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

typedef struct _D3DMATERIAL9 { D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive; float Power; } D3DMATERIAL9;

Diffuse — Задает количество отражаемого поверхностью рассеиваемого света.

Ambient — Задает количество отражаемого поверхностью фонового света.

Specular — Задает количество отражаемого поверхностью отражаемого света.

Emissive — Данный компонент позволяет увеличивать значения цветов, что создает эффект свечения поверхности.

Power — Задает резкость зеркальных отражений; чем больше значение, тем более резкими будут отражения.

Предположим. для примера, что мы хотим создать красный шар. Мы должны определить материал шара таким образом, чтобы он отражал только красную составляющую света, а все остальные поглощал:

D3DMATERIAL9 red; ::ZeroMemory(&red, sizeof(red)); red.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // красный red.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // красный red.Specular = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // красный red.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); // нет свечения red.Power = 5.0f;

Здесь мы присваиваем зеленой и синей составляющим 0, указывая, что материал полностью поглощает лучи света данных цветов. Красной составляющей мы присваиваем значение 1, а это значит, что лучи данного цвета полностью отражаются поверхностью. Обратите внимание,что мы можем контроллировать степень отражения для каждой составляющей освещения (фоновой, рассеиваемой и отражаемой).

Также обратите внимание, что если мы создаем источник света испускающий лучи только синего цвета, то результат освещения данного шара может нас разочаровать, поскольку шар полностью поглощает лучи синего цвета а лучи красного цвета на него не попадают. Если объект поглощает все падающие на него лучи света, то он выглядит черным. Точно так же если объект полностью отражает все лучи (красный, синий и зеленый), то он выглядит белым. Поскольку ручное задание параметров материалов является скучным занятием, мы добавим в файлы d3dUtility.h/cpp несколько вспомогательных функций и глобальных констант материалов:

D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p) { D3DMATERIAL9 mtrl; mtrl.Ambient = a; mtrl.Diffuse = d; mtrl.Specular = s; mtrl.Emissive = e; mtrl.Power = p; return mtrl; }

namespace d3d { . . . D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);

const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 8.0f);

const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 8.0f);

const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 8.0f);

const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 8.0f);

const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 8.0f); }

ПРИМЕЧАНИЕ

По адресу http://www.adobe.com/support/techguides/color/colortheory/main.html находится замечательная статья, посвященная теории цвета, освещению и тому, как человеческий глаз различает цвета.


Зональный свет (spot light)— Источник света данного типа похож на фонарик; у него есть местоположение и он испускает конический сноп лучей в заданном направлении. Световой конус характеризуется двумя углами — φ и θ. Угол φ задает размер внутреннего конуса, а угол θ — внешнего.



Рисование с буферами вершин и индексов


После того, как мы создали наши буферы вершин и индексов и выполнили все подготовительные действия, можно рисовать наши фигуры, передавая данные об их геометрии в конвейер визуализации с помощью методов DrawPrimitive или DrawIndexedPrimitive. Эти методы получают информацию о вершинах из настроенного потока вершин и индексы из установленного в данный момент буфера индексов.



Рисунок Объединение раздельно объявленных буферов вершин в один общий буфер вершин



Рисунок  3.2. Объединение раздельно объявленных буферов вершин в один общий буфер вершин



Нормали вершин


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



Нормали вершин поверхности



Рисунок 5.2. Нормали вершин поверхности

Direct3D необходимо знать нормали вершин, поскольку они необходимы чтобы оперделить под каким углом свет падает на грань. Кроме того, поскольку вычисление освещенности выполняется для каждой из вершин, Direct3D необходимо знать ориентацию грани (нормаль) для каждой вершины. Обратите внимание, что нормаль вершины не всегда совпадает с нормалью грани. Наиболее распростарненным примером объекта у которого нормали треугольных граней не совпадают с нормалями вершин является сфера или цилиндр (Рисунок 5.3).



Окно приложения Cube



Рисунок 3.4. Окно приложения Cube


Сперва мы объявляем две глобальных переменных, которые будут хранить данные вершин и индексов нашего куба:

IDirect3DVertexBuffer9* VB = 0; IDirect3DIndexBuffer9* IB = 0;

Кроме того, мы объявляем две глобальных константы, задающих разрешение экрана:

const int Width = 800; const int Height = 600;

Затем мы описываем структуру для хранения данных вершин и приводим описание настраиваемого формата вершин для этой структуры. В данном примере структура данных вершины содержит только информацию о местоположении вершины:

struct Vertex { Vertex(){} Vertex(float x, float y, float z) { _x = x; _y = y; _z = z; } float _x, _y, _z; static const DWORD FVF; }; const DWORD Vertex::FVF = D3DFVF_XYZ;

Давайте перейдем к функциям, образующим каркас приложения. Функция Setup создает буфер вершин и буфер индексов, блокирует их, записывает в буфер вершин данные образующих куб вершин, а в буфер индексов — индексы, описывающие образующие куб треугольники. Затем камера отодвигается на несколько единиц назад, чтобы мы могли увидеть куб, расположенный в начале мировой системы координат. После этого устанавливается преобразование проекции. В самом конце мы включаем каркасный режим визуализации:

bool Setup() { // Создание буфера вершин и буфера индексов Device->CreateVertexBuffer( 8 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &VB, 0);

Device->CreateIndexBuffer( 36 * sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &IB, 0);

// Заполнение буферов данными куба Vertex* vertices; VB->Lock(0, 0, (void**)&vertices, 0);

// Вершины единичного куба vertices[0] = Vertex(-1.0f, -1.0f, -1.0f); vertices[1] = Vertex(-1.0f, 1.0f, -1.0f); vertices[2] = Vertex( 1.0f, 1.0f, -1.0f); vertices[3] = Vertex( 1.0f, -1.0f, -1.0f); vertices[4] = Vertex(-1.0f, -1.0f, 1.0f); vertices[5] = Vertex(-1.0f, 1.0f, 1.0f); vertices[6] = Vertex( 1.0f, 1.0f, 1.0f); vertices[7] = Vertex( 1.0f, -1.0f, 1.0f);


VB->Unlock();

// Описание образующих куб треугольников WORD* indices = 0; IB->Lock(0, 0, (void**)&indices, 0);

// передняя грань indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 0; indices[4] = 2; indices[5] = 3;

// задняя грань indices[6] = 4; indices[7] = 6; indices[8] = 5; indices[9] = 4; indices[10] = 7; indices[11] = 6;

// левая грань indices[12] = 4; indices[13] = 5; indices[14] = 1; indices[15] = 4; indices[16] = 1; indices[17] = 0;

// правая грань indices[18] = 3; indices[19] = 2; indices[20] = 6; indices[21] = 3; indices[22] = 6; indices[23] = 7;

// верх indices[24] = 1; indices[25] = 5; indices[26] = 6; indices[27] = 1; indices[28] = 6; indices[29] = 2;

// низ indices[30] = 4; indices[31] = 0; indices[32] = 3; indices[33] = 4; indices[34] = 3; indices[35] = 7;

IB->Unlock();

// размещение и ориентация камеры D3DXVECTOR3 position(0.0f, 0.0f, -5.0f); D3DXVECTOR3 target(0.0f, 0.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &target, &up);

Device->SetTransform(D3DTS_VIEW, &V);

// установка матрицы проекции D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.5f, // 90 градусов (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj);

// установка режима визуализации Device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

return true; }

У метода Display две задачи: он должен обновлять сцену и затем визуализировать ее. Поскольку мы хотим, чтобы куб вращался, нам надо в каждом кадре увеличивать угол, определяющий насколько повернут куб. Из-за того, что угол в каждом кадре немного увеличивается, куб в каждом кадре чуть больше повернут, и в результате кажется, что он вращается. Обратите внимание, что для ориентации куба мы применяем мировое преобразование. Затем мы рисуем куб с помощью метода IDirect3DDevice9::DrawIndexedPrimitive.

bool Display(float timeDelta) { if( Device ) { // // вращение куба: // D3DXMATRIX Rx, Ry;



// поворот на 45 градусов вокруг оси X D3DXMatrixRotationX(&Rx, 3.14f / 4.0f);

// увеличение угла поворота вокруг оси Y в каждом кадре static float y = 0.0f; D3DXMatrixRotationY(&Ry, y); y += timeDelta;

// сброс угла поворота, если он достиг 2*PI if( y >= 6.28f ) y = 0.0f;

// комбинация поворотов D3DXMATRIX p = Rx * Ry;

Device->SetTransform(D3DTS_WORLD, &p);

// // рисование сцены: // Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene();

Device->SetStreamSource(0, VB, 0, sizeof(Vertex)); Device->SetIndices(IB); Device->SetFVF(Vertex::FVF); Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12);

Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }

Когда приложение завершает работу, мы освобождаем выделенную память. Это значит, что мы освобождаем интерфейсы буфера вершин и буфера индексов:

void Cleanup() { d3d::Release<IDirect3DVertexBuffer9*>(VB); d3d::Release<IDirect3DIndexBuffer9*>(IB); }


Окно приложения LitPyramid



Рисунок 5.7. Окно приложения LitPyramid


Вот действия, которые необходимо выполнить, чтобы добавить к сцене источник света:

Разрешить освещение.

Создать материал для каждого объекта и задать используемый материал перед визуализацией соответствующего объекта.

Создать один или несколько источников света, расставить их на сцене и включить их.

Если необходимо, включить дополнительные режимы освещения, например, обработку отражаемого света.

Сперва мы объявляем глобальный буфер вершин в котором будут храниться данные вершин пирамиды:

IDirect3DVertexBuffer9* Pyramid = 0;

Весь код, относящийся к рассматриваемой в данной главе теме, находится в функции Setup, поэтому для экономии места остальные функции мы рассматривать не будем. В функции Setup реализованы перечисленные выше этапы добавления к сцене освещения. Начинается функция с разрешения использования освещения, которое не является необходимым, поскольку по умолчанию освещение разрешено (но хуже от этого не будет).

bool Setup() { Device->SetRenderState(D3DRS_LIGHTING, true);

Затем мы создаем буфер вершин, блокируем его и задаем данные вершин, образующих треугольную пирамиду. Нормали вершин вычисляются с помощью описанного в разделе 5.3 алгоритма. Обратите внимание, что хотя треугольники совместно используют некоторые вершины, нормали у каждого из них свои; так что использование для данного объекта списка индексов не принесет никакой пользы. Например, все грани используют вершину (0, 1, 0), находящуюся на верху пирамиды; однако нормаль этой вершины у каждого треугольника указывает в своем направлении.

Device->CreateVertexBuffer( 12 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &Pyramid, 0);

// Заполняем буфер вершин данными пирамиды Vertex* v; Pyramid->Lock(0, 0, (void**)&v, 0);

// передняя грань v[0] = Vertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f); v[1] = Vertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, -0.707f); v[2] = Vertex( 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);


// левая грань v[3] = Vertex(-1.0f, 0.0f, 1.0f, -0.707f, 0.707f, 0.0f); v[4] = Vertex( 0.0f, 1.0f, 0.0f, -0.707f, 0.707f, 0.0f); v[5] = Vertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);

// правая грань v[6] = Vertex( 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f); v[7] = Vertex( 0.0f, 1.0f, 0.0f, 0.707f, 0.707f, 0.0f); v[8] = Vertex( 1.0f, 0.0f, 1.0f, 0.707f, 0.707f, 0.0f);

// задняя грань v[9] = Vertex( 1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f); v[10] = Vertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, 0.707f); v[11] = Vertex(-1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);

Pyramid->Unlock();

После того, как данные вершин для нашего объекта сгенерированы, мы описываем взаимодействие объекта со световыми лучами путем задания материала. В данном примере пирамида отражает белый свет, сама не испускает света и формирует блики.

D3DMATERIAL9 mtrl; mtrl.Ambient = d3d::WHITE; mtrl.Diffuse = d3d::WHITE; mtrl.Specular = d3d::WHITE; mtrl.Emissive = d3d::BLACK; mtrl.Power = 5.0f;

Device->SetMaterial(&mtrl);

Теперь мы создаем и включаем источник направленного света. Лучи направленного света распространяются параллельно оси X в положительном направлении. Рассеиваемая составляющая света окрашена в белый цвет и имеет максимальную интенсивность (dir.Diffuse = WHITE), отражаемая составляющая также белого цвета, но малой интенсивности (dir.Specular = WHITE * 0.3f), а фоновая составляющая — белого цвета и средней интенсивности (dir.Ambient = WHITE * 0.6f).

D3DLIGHT9 dir; ::ZeroMemory(&dir, sizeof(dir));

dir.Type = D3DLIGHT_DIRECTIONAL; dir.Diffuse = d3d::WHITE; dir.Specular = d3d::WHITE * 0.3f; dir.Ambient = d3d::WHITE * 0.6f; dir.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);

Device->SetLight(0, &dir); Device->LightEnable(0, true);

И, наконец, мы устанавливаем режимы визуализации для ренормализации нормалей и разрешения обработки отражаемой составляющей света.

Device->SetRenderState(D3DRS_NORMALIZENORMALS, true); Device->SetRenderState(D3DRS_SPECULARENABLE, true);

// ... код инициализации матрицы вида и матрицы проекции опущен

return true; }


Подготовка к рисованию


Создав буфер вершин и, возможно, буфер индексов мы уже почти готовы к визуализации их содержимого, но сперва надо выполнить несколько подготовительных действий.

Установка источника потоковых данных. Эта операция подключает буфер вершин к потоку, основная задача которого — снабжение конвейера визуализации данными о геометрии.

Для установки источника потоковых данных используется следующий метод:

HRESULT IDirect3DDevice9::SetStreamSource( UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride );

StreamNumber — Идентифицирует поток, к которому мы будем подключать буфер вершин. В этой книге мы не используем несколько потоков, так что значение данного параметра всегда будет равно нулю.

pStreamData — Указатель на буфер вершин,который мы хотим подключить к потоку.

OffsetInBytes — Измеренное в байтах смещение от начала потока, задающее начало данных вершин, которые будут переданы в конвейер визуализации. Если вы собираетесь указывать отличное от нуля значение, убедитесь, что устройство поддерживает данную возможность, проверив установлен ли флаг D3DDEVCAPS2_STREAMOFFSET в структуре D3DCAPS9.

Stride — Размер в байтах каждого элемента того буфера вершин, который мы подключаем к потоку.

Предположим, что vb — это буфер вершин, заполненный данными вершин типа Vertex. В этом случае обращение к методу будет выглядеть так:

_device->SetStreamSource(0, vb, 0, sizeof(Vertex));

Задание формата вершин. Здесь мы указываем формат вершин, который будет использоваться в дальнейших вызовах функций рисования.

_device->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);

Задание буфера индексов. Если мы используем буфер индексов, то должны указать тот буфер индексов, который будет использоваться в последующих операциях рисования. Одновременно может использоваться только один буфер индексов; следовательно, если вам потребуется нарисовать объект с использованием другого буфера индексов, надо будет переключиться на другой буфер. Задание буфера индексов выполняет следующий фрагмент кода:

_device->SetIndices(_ib); // передаем копию указателя на буфер индексов



Получение информации о буфере



3.1.3. Получение информации о буфере

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

D3DVERTEXBUFFER_DESC vbDescription; _vertexBuffer->GetDesc(&vbDescription); // получаем информацию о буфере вершин

D3DINDEXBUFFER_DESC ibDescription; _indexBuffer->GetDesc(&ibDescription); // получаем информацию о буфере индексов

Объявление структур D3DVERTEXBUFFER_DESC и D3DINDEXBUFFER_DESC выглядит следующим образом:

typedef struct _D3DVERTEXBUFFER_DESC { D3DFORMAT Format; D3DRESOURCETYPE Type; DWORD Usage; D3DPOOL Pool; UINT Size; DWORD FVF; } D3DVERTEXBUFFER_DESC;

typedef struct _D3DINDEXBUFFER_DESC { D3DFORMAT Format; D3DRESOURCETYPE Type; DWORD Usage; D3DPOOL Pool; UINT Size; } D3DINDEXBUFFER_DESC;



Пример объекта, у которого нормали



Рисунок 5.3. Пример объекта, у которого нормали вершин не совпадают с нормалями граней. Векторы нормалей вершин выделены черным цветом, а векторы нормалей граней — серым

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

struct Vertex { float _x, _y, _z; float _nx, _ny, _nz; static const DWORD FVF; } const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;

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

Для простых объектов, таких как кубы и сферы, нормали вершин можно оперделить путем осмотра. Для сложных сеток необходим более алгоритмизированный способ. Предположим, что треугольник образован вершинами p0, p1 и p2, и нам необходимо вычислить нормали n0, n1 и n2 для каждой из вершин.

Простейший подход заключается в том, чтобы вычислить нормаль грани для треугольника и использовать ее в качестве нормали для всех трех вершин. Сперва вычислим два вектора, лежащих в полскости треугольника:



Тогда нормаль грани вычисляется по формуле:



Поскольку нормаль каждой вершины совпадает с нормалью грани:



Ниже приведена функция, которая вычисляет нормаль треугольной грани на основании координат трех ее вершин. Обратите внимание, что функция предполагает, что вершины перечислены по часовой стрелке. Если это не так, нормаль будет указывать в противоположном направлении.

void ComputeNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2, D3DXVECTOR3* out) { D3DXVECTOR3 u = *p1 - *p0; D3DXVECTOR3 v = *p2 - *p0;

D3DXVec3Cross(out, &u, &v); D3DXVec3Normalize(out, out); }

Использование нормали грани в качестве нормалей вершин не позволяет добиться гладкого изображения состоящих из треугольных граней сложных кривых поверхностей. Лучшим методом вычисления нормалей вершин является усреднение нормалей (normal averaging). Чтобы вычислить вектор нормали vn для вершины v, мы вычисляем нормали граней всех треугольников сетки, в которые входит данная вершина v. Затем вектор нормали вершины vn получается путем вычисления среднего значения всех этих нормалей граней. Давайте разберем конкретный пример. Предположим, вершина v входит в три треугольника, для которых известны их нормали граней n0, n1 и n2. Тогда vn вычисляется путем усреднения нормалей граней:



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

Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);



Пример приложения: освещенная пирамида


Пример приложения из этой главы создает сцену, показанную на Рисунок 5.7. Он показывает как задать нормали вершин, как создать материал и как создать и активизировать источник направленного света. Обратите внимание, что в этом примере мы не пользуемся функциями и объявлениями для материала и источника света из файлов d3dUtility.h/cpp потому что хотим сперва показать вам как можно всю работу выполнить вручную. Однако в остальных примерах из этой книги мы будем пользоваться вспомогательными функциями для задания материалов и источников света.



Примеры приложений: треугольники, кубы, чайники, DXCreate*


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

Triangle— Это очень простое приложение, демонстрирующее как создать и визуализировать в каркасном режиме треугольник.

Cube — Чуть более сложное, чем вариант с треугольником, данное приложение отображает визуализированный в каркасном режиме вращающийся куб.

Teapot — Это приложение использует функцию D3DXCreateTeapot, чтобы создать и отобразить вращающийся чайник.

D3DXCreate — Приложение создает и визуализирует несколько различных трехмерных объектов, которые можно создать с помошью функций D3DXCreate*.

Давайте кратко обсудим реализацию примера Cube. Остальные примеры вы можете изучить самостоятельно.

Приложение отображает куб, как показано на Рисунок  3.4. Проект и его полный исходный код находятся в сопроводительных файлах, которые можно загрузить с веб-сайта этой книги.



Режимы визуализации

В Direct3D включен набор режимов визуализации (rendering state), которые влияют на способ рисования объектов. У каждого режима визуализации есть значение по умолчанию, так что вам надо менять их только в том случае, если требуемый приложению режим визуализации отличается от предлагаемого по умолчанию. Установленный режим визуализации влияет на процесс рисования до тех пор, пока вы снова не измените его. Для установки режимов визуализации используется следующий метод:

HRESULT IDirect3DDevice9::SetRenderState( D3DRENDERSTATETYPE State, // изменяемый режим DWORD Value // новое значение режима );

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

_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

ПРИМЕЧАНИЕ

Чтобы увидеть все доступные режимы визуализации, посмотрите описание типа D3DRENDERSTATETYPE в DirectX SDK.



Рисунок Объекты, создаваемые и визуализируемые с использованием функций DXCreate*



Рисунок  3.3. Объекты, создаваемые и визуализируемые с использованием функций D3DXCreate*

<
Все шесть функций применяются одинаково и используют структуру данных сетки D3DX ID3DXMesh и интерфейс ID3DXBuffer. Подробнее об этих интерфейсах мы поговорим в главе 10 и главе 11. Сейчас мы проигнорируем детали их функционирования и сосредоточимся на простейшем варианте их использования.

HRESULT D3DXCreateTeapot( LPDIRECT3DDEVICE9 pDevice, // связанное с сеткой устройство LPD3DXMESH* ppMesh, // указатель на полученную сетку LPD3DXBUFFER* ppAdjacency // сейчас присвоить ноль );

Вот пример использования функции D3DXCreateTeapot:

ID3DXMesh* mesh = 0; D3DXCreateTeapot(_device, &mesh, 0);

После того, как мы сгенерировали данные сетки, можно нарисовать ее с помощью метода ID3DXMesh::DrawSubset. Единственный параметр этого метода идентифицирует подгруппу сетки. Генерируемые функциями D3DXCreate* сетки состоят из одной подгруппы, так что в этом параметре передается ноль. Вот пример визуализации сетки:

_device->BeginScene(); mesh->DrawSubset(0); _device->EndScene();

Когда вы завершите работу с сеткой, освободите ее:

_mesh->Release(); _mesh = 0;


Рисунок Параметры OffsetToLock



Рисунок  3.1. Параметры OffsetToLock и SizeToLock определяют блокируемую область памяти. Если обоим параметрам присвоить нулевые значения, будет заблокирован весь буфер

Параметры у обоих методов одинаковы.

OffsetToLock — смещение в байтах от начала буфера до начала блокируемой области (Рисунок  3.1).

SizeToLock — количество блокируемых байтов.

ppbData — Адрес для возврата указателя на начало заблокированной области памяти.

Flags — Флаги, задающие режим блокировки. Можно указать ноль, либо комбинацию из одного или нескольких перечисленных ниже флагов:

D3DLOCK_DISCARD — Этот флаг используется только для динамических буферов. Он приказывает аппаратуре оставить старый буфер и возвратить указатель на новый буфер, который должен быть создан. Данная возможность полезна потому что позволяет аппаратуре продолжать визуализацию с использованием старого буфера в то время как пользователь работает с новым буфером. Это предотвращает простои оборудования.

D3DLOCK_NOOVERWRITE — Данный флаг используется только для динамических буферов. Он сообщает, что вы будете только добавлять данные в буфер. Это значит, что вы не будете перезаписывать никакие данные, которые уже используются для визуализации. Следовательно видеокарта может продолжать выполнять визуализацию, в то время когда вы добавляете новые данные в буфер.

D3DLOCK_READONLY — Этот флаг указывает, что вы хотите заблокировать буфер только для чтения и ничего не будете записывать в него. Благодаря этому система может выполнить внутреннюю оптимизацию.

Флаги D3DLOCK_DISCARD и D3DLOCK_NOOVERWRITE опираются на тот факт, что в момент вызова функции блокировки часть памяти буфера может использоваться для визуализации. Если обстоятельства позволяют указывать эти флаги, то благодаря их использованию можно избежать простоев визуализации, которые могли бы произойти в ином случае.

Приведенный ниже пример демонстрирует обычный вариант использования метода Lock. Обратите внимание, что закончив работу мы сразу вызываем метод Unlock.

Vertex* vertices; _vb->Lock(0, 0, (void**)&vertices, 0); // заблокировать весь буфер

vertices[0] = Vertex(-1.0f, 0.0f, 2.0f); // записать данные вершин vertices[1] = Vertex( 0.0f, 1.0f, 2.0f); // в буфер vertices[2] = Vertex( 1.0f, 0.0f, 2.0f);

_vb->Unlock(); // разблокировать буфер, когда // мы закончили работать с ним



Создание буферов вершин и индексов



3.1.1. Создание буферов вершин и индексов

Для создания буферов вершин и индексов используются следующие два метода:

HRESULT IDirect3DDevice9::CreateVertexBuffer( UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle );

HRESULT IDirect3DDevice9::CreateIndexBuffer( UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer, HANDLE* pSharedHandle );

У обоих методов большая часть параметров идентична, так что рассмотрим параметры обоих методов вместе:

Length — Количество байт, выделяемых под буфер. Если нам требуется буфер достаточного размера для хранения восьми вершин, в этом параметре следует указать 8 * sizeof(Vertex), где Vertex — это ваша структура данных вершины.

Usage — Задает ряд дополнительных параметров, определяющих особенности использования буфера. Можно указать 0, если дополнительные параметры отсутствуют, или комбинацию из одного или нескольких следующих флагов:

D3DUSAGE_DYNAMIC — Флаг указывает, что данный буфер будет динамическим. Различия между статическими и динамическими буферами мы обсудим чуть позже.

D3DUSAGE_POINTS — Флаг указывает, что буфер будет использовваться для хранения примитивов точек. Примитивы точек рассматриваются в главе 14. Этот флаг может применяться только для буферов вершин.

D3DUSAGE_SOFTWAREPROCESSING — Обработка вершин будет выполняться программно.

D3DUSAGE_WRITEONLY — Указывает, что приложение может только записывать данные в буфер. Это позволяет драйверу разместить буфер в области памяти, обеспечивающей максимальную скорость записи. Обратите внимание, что попытка прочитать данные из буфера, созданного с указанием этого флага, приведет к ошибке.

FVF — Настраиваемый формат вершин, которые будут храниться в создаваемом буфере вершин.

Pool — Пул памяти, в котором будет размещен буфер.

ppVertexBuffer — Адрес ля возврата указателя на созданный буфер вершин.

pSharedHandle — Не используется, должен быть равен 0.

Format — Задает размер индексов; используйте D3DFMT_INDEX16 для 16-разрядных индексов или D3DFMT_INDEX32 для 32-разрядных индексов. Обратите внимание, что не все устройства поддерживают 32-разрядные индексы, не забывайте проверять возможности устройства.

ppIndexBuffer — Адрес для возврата указателя на созданный буфер индексов.

ПРИМЕЧАНИЕ

Буфер, созданный без указания флага D3DUSAGE_DYNAMIC называется статическим буфером. Обычно статический буфер располагается в видеопамяти, где его содержимое может обрабатываться более эффективно. Однако, если вы сделаете буфер статическим, придется расплачиваться исключительно низкой скоростью записи в буфер и чтения из него. Поэтому мы будем использовать статические буферы для хранения статических данных (данных, которые не надо часто изменять). Хорошими кандидатами на размещение в статическом буфере являются ландшафты и здания, поскольку их геометрия обычно не изменяется во время работы приложения. Данные геометрии должны заноситься в статические буферы во время инициализации приложения, а не во время его работы. ПРИМЕЧАНИЕ

Буфер, созданный с указанием флага D3DUSAGE_DYNAMIC называется динамическим буфером. Динамические буферы обычно располагаются в памяти AGP, где их содержимое может достаточно быстро обновляться. Видеокарта работает с динамическими буферами медленнее, чем со статическими, потому что данные перед визуализацией должны быть переданы из буфера в видеопамять. Главное преимущество динамических буферов — возможность быстрого обновления (быстрая запись данных центральным процессором). Следовательно, если вам надо часто изменять содержимое буфера, сделайте его динамическим. Хорошим кандидатом на размещение в динамическом буфере являются системы частиц, поскольку они анимируются и их геометрия обычно изменяется в каждом кадре. ПРИМЕЧАНИЕ

Чтение данных из видеопамяти или из памяти AGP выполняется очень медленно.Следовательно, если вам во время работы приложения надо читать данные геометрии, лучше всего создать копию данных в системной памяти и обращаться для чтения к ней. В приведенном ниже фрагменте кода создается статический буфер вершин в котором можно хранить до восьми вершин типа Vertex.

IDirect3DVertexBuffer9* vb; _device->CreateVertexBuffer( 8 * sizeof(Vertex), 0, D3DFVF_XYZ, D3DPOOL_MANAGED, &vb, 0);

Следующий фрагмент кода показывает как создать динамический буфер индексов, в котором можно хранить до 36 16-разрядных индексов.

IDirect3DIndexBuffer9* ib; _device->CreateIndexBuffer( 36 * sizeof(WORD), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &ib, 0);


Точечный свет



Рисунок 5.4. Точечный свет


Направленный свет (directional light) — У этого источника света нет местоположения, он испускает параллельные световые лучи в заданном направлении.



Зональный свет



Рисунок 5.6. Зональный свет


В коде источники света представляются структурой D3DLIGHT9.

typedef struct _D3DLIGHT9 { D3DLIGHTTYPE Type; D3DCOLORVALUE Diffuse; D3DCOLORVALUE Specular; D3DCOLORVALUE Ambient; D3DVECTOR Position; D3DVECTOR Direction; float Range; float Falloff; float Attenuation0; float Attenuation1; float Attenuation2; float Theta; float Phi; } D3DLIGHT9;

Type — Задает тип источника света и может принимать одно из трех значений: D3DLIGHT_POINT, D3DLIGHT_SPOT или D3DLIGHT_DIRECTIONAL.

Diffuse — Цвет рассеиваемой составляющей испускаемого источником света.

Specular — Цвет отражаемой составляющей испускаемого источником света.

Ambient — Цвет фоновой составляющей испускаемого источником света.

Position — Вектор, задающий местоположение источника света в пространстве. Для направленного света значение не используется.

Direction — Вектор, задающий направление, в котором распространяется свет. Для точечного света не используется.

Range — Максимальное расстояние, на которое может распространиться свет прежде чем окончательно потухнет. Значение не может быть больше чем √FLT_MAX и не оказывает влияния на направленный свет.

Falloff — Значение используется только для зонального света. Оно определяет как меняется интенсивность света в пространстве между внутренним и внешним конусами. Обычно этому параметру присваивают значение 1.0f.

Attenuation0, Attenuation1, Attenuation2 — Переменные затухания, определяющие как меняется интенсивность света с увеличением расстояния до источника света. Эти переменные используются только для точечного и зонального света. Переменная Attenuation0 задает постоянное затухание, Attenuation1 — линейное затухание и Attenuation2 — квадратичное затухание. Вычисления выполняются по формуле



где D — это расстояние от источника света, а A0, A1, A2 соответственно Attenuation0, Attenuation1 и Attenuation2.


Theta — Используется только для зонального света; задает угол внутреннего конуса в радианах.

Phi — Используется только для зонального света; задает угол внешнего конуса в радианах.

Подобно инициализации структуры D3DMATERIAL9, в том случае, когда нам нужны только простые источники света, инициализация структуры D3DLIGHT9 становится рутинным занятием. Поэтому для инициализации простых источников света мы добавим в файлы d3dUtility.h/cpp следующие функции:

namespace d3d { . . . D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);

D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);

D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color); }

Реализация этих функций не содержит сложных моментов. Мы рассмотрим только реализацию InitDirectionalLight. Остальные функции похожи на нее:

D3DLIGHT9 d3d::InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color) { D3DLIGHT9 light; ::ZeroMemory(&light, sizeof(light));

light.Type = D3DLIGHT_DIRECTIONAL; light.Ambient = *color * 0.4f; light.Diffuse = *color; light.Specular = *color * 0.6f; light.Direction = *direction;

return light; }

Теперь для создания источника направленного света белого цвета, испускающего лучи вдоль оси X в положительном направлении, можно написать:

D3DXVECTOR3 dir(1.0f, 0.0f, 0.0f); D3DXCOLOR c = d3d::WHITE; D3DLIGHT9 dirLight = d3d::InitDirectionalLight(&dir, &c);

После того, как мы инициализировали экземпляр D3DLIGHT9, нам надо зарегистрировать его во внутреннем списке управляемых Direct3D источников света. Делается это вот так:

Device->SetLight( 0, // устанавливаемый элемент списка источников света, диапазон 0 - maxlights &light);// адрес инициализированной структуры D3DLIGHT9

После регистрации источника света мы можем включать его и выключать, как показано в приведенном ниже фрагменте кода:

Device->LightEnable( 0, // Включаемый или выключаемый источник света в списке true); // true = включить, false = выключить