Программирование графических процессоров с использованием Direct3D и HLSL

Освещенность и материалы. Построения объектов

Разбить на страницы
Показывать лекцию целиком

Освещенность и материалы

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

При расчете освещенности грани в компьютерной графике учитывают следующие типы отражения света от поверхности: рассеянное, диффузное, зеркальное. Интенсивность освещения граней объектов рассеянным светом считается постоянной в любой точке пространства. Это обусловлено тем, что рассеянный свет создается многочисленными отражениями от различных поверхностей пространства. Такой свет практически всегда присутствует в реальной обстановке. Даже если объект защищен от прямых лучей, исходящих от точечного источника света, он все равно будет виден из-за наличия рассеянного света. Интенсивность рассеянного света выражается как $$I=I_{a}k_{a}$$, где $$I_{a}$$ - интенсивность рассеянного света, $$k_{a} \in [0,1]$$ - коэффициент рассеянного отражения, зависящий от отражательных свойств материала (поверхности). При освещении объекта только рассеянным светом, все его грани будут закрашены одинаково, а общие ребра будут неразличимы.

При наличии в сцене точечного источника света, интенсивность диффузного отражения пропорциональна косинусу угла между нормалью к поверхности и направлением на источник света. В этом случае для вычисления интенсивности диффузного отражения применяют закон косинусов Ламберта: $$I=I_{p}k_{d}cos \theta$$, где $$I_{p}$$ - интенсивность точечного источника света, $$k_{d} \in [0,1]$$ - коэффициент диффузного отражения, $$\theta$$ - угол падения, рассчитываемый как угол между направлением на источник света и нормалью к поверхности. Если вектора N, L нормализованы, т.е. |N|=|L|=1, то $$\cos \theta =(\overrightarrow N \overrightarrow L)$$ - скалярное произведение векторов, и уравнение можно записать в виде $$I_p k_d (\overline N \cdot \overline L)$$.

Свойством диффузного отражения является равномерность по всем направлениям отраженного света. Поэтому такие объекты имеют одинаковую яркость, вне зависимости от угла обзора. К примеру, матовые поверхности обладают таким свойством. Предположим, что у нас имеется два полигона, одинаково ориентированы относительно источника света, но расположенные на разных расстояниях от него. Если вычислить значения интенсивностей освещенности для каждого полигона, по приведенной формуле, то они окажутся одинаковыми. Это означает, что объекты, расположенные на разном расстоянии от источника света, будут освещаться независимо от их местоположения в трехмерной сцене. В этом случае интенсивность света должна быть обратнопропорциональна квадрату расстояния от источника до грани объекта. Тогда объект, расположенный дальше от источника будет темнее. Тем не менее на практике, как правило, используют не квадрат расстояния, а линейное затухание. В этом случае формула общей интенсивности может быть записана как:$$I=I_ak_a+\frac{I_p k_d (\overline N \cdot \overline L)}{d+k},$$ где d - расстояние от центра проекции (наблюдателя) до поверхности, k - некоторая константа, k>=1.

Зеркальное отражение можно получить от любой блестящей поверхности. Осветите ярким светом обычное яблоко, и световой блик возникнет в результате зеркального отражения, а свет, отраженный от остальной части яблока, появится вследствие диффузного отражения. Следует отметить, что в том месте, где находится световой блик, яблоко будет казаться не красным, а, скорее всего – белым, т.е. окрашенным в цвет падающего света. Если изменить положение наблюдателя, то световой блик тоже сместится. Это объясняется тем, что блестящие поверхности отражают свет неодинаково по всем направлениям. От идеально зеркальной поверхности свет отражается только в том направлении, для которого углы падения и отражения совпадают. Зеркально отраженный свет можно будет увидеть, если угол между вектором отражения (на рисунке обозначен R ) и вектором наблюдения (на рисунке обозначен S ) равен нулю.

Для неидеальных отражающих поверхностей интенсивность отраженного света резко падает с ростом $$\alpha.$$ В модели, предложенной Фонгом, быстрое убывание интенсивности света, описывается функцией $$cos^{n}\alpha$$, где $$n \in [1,200]$$ в зависимости от вида поверхности. Для идеальной отражающей поверхности $$n\to\infty$$. Интенсивность зеркального отражения света по модели Фонга записывается как $$I=I_{p}*k_{S}*cos^{n} \alpha$$, где $$I_{p}$$ - интенсивность источника света, $$k_{S} \in [0,1]$$ - коэффициент зеркального отражения. Если вектора R и S нормированы, то формула может быть преобразована к виду:$$I=I_{pks} (\overline R \cdot \overline S)^n.$$ Интенсивность зеркального света обратно пропорциональна расстоянию от источника до грани. Для практических задач здесь используют также модель линейного затухания света, которая выражается следующей формулой:$$I=\frac{I_{pks} (\overline R \cdot \overline S)}{d+k}^n,$$ где d - расстояние от наблюдателя до поверхности, k - некоторая константа.

Таким образом, интенсивность грани складывается из трех составляющих: рассеянного света, диффузного света и зеркального света. Формула расчета общей интенсивности с учетом расстояния от наблюдателя до освещенной грани и трех составляющих света записывается как$$I=I_ak_a+\frac{I_p}{d+k}(k_d (\overline L \cdot \overline N)+k_s (\overline R \cdot \overline S)^n).$$

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

Для работы с материалом предусмотрен специальный тип данных D3DMATERIAL9, который содержит следующие поля.

C++
typedef struct _D3DMATERIAL9 {
  D3DCOLORVALUE Diffuse;   {рассеянный свет, исходящий от материала}
  D3DCOLORVALUE Ambient;   {определяет окружающий свет}
  D3DCOLORVALUE Specular;  {определяет отражающий (зеркальный) свет}
  D3DCOLORVALUE Emissive;  {определяет излучающий свет материала}
  float Power;             {мощность отражения}
} D3DMATERIAL9;
Pascal
TD3DMaterial9 = packed record
 Diffuse: TD3DColorValue;  {рассеянный свет, исходящий от материала}
 Ambient: TD3DColorValue;  {определяет окружающий свет}
 Specular: TD3DColorValue; {определяет отражающий (зеркальный) свет}
 Emissive: TD3DColorValue; {определяет излучающий свет материала}
 Power: Single;            {мощность отражения}
end;

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

  • Объявление переменной типа D3DMATERIAL9 ;
  • Заполнение соответствующих полей данной структуры;
  • Установка материала.
  • Программно эти шаги реализуются таким образом:

    C++
    D3DMATERIAL9 material;
    ZeroMemory( material, sizeof(D3DMATERIAL9) );
    material.Diffuse=D3DXCOLOR(0.7f, 0.0f, 0.0f, 0.0f);
    material.Ambient=D3DXCOLOR(0.2f, 0.0f, 0.0f, 0.0f);
    material.Specular=D3DXCOLOR(0.1f, 0.0f, 0.0f, 0.0f);
    ...
    device->SetMaterial( material );
    Pascal
    var 
       material: TD3DMaterial9;
    ...
    ZeroMemory(@material,SizeOf(TD3DMaterial9));
    material.Diffuse:=D3DXColor(0.7, 0, 0, 0); // r, g, b, a
    material.Ambient:=D3DXColor(0.2, 0, 0, 0);
    material.Specular:=D3DXColor(0.1, 0, 0, 0);
    ...
    device.SetMaterial(material);

    Метод SetMaterial вызывается при выводе объектов (в процедуре рендеринга). Кроме определения свойств материала необходимо задать источники света. Библиотека Direct3D поддерживает три типа источника света:

  • Параллельный (направленный) – этот тип не имеет определенного источника света, он как бы повсюду, но светит в одном направлении.
  • Точечный – источник, светящий во всех направлениях (лампочка).
  • Прожекторный (нацеленный) – тип, имеющий определенный источник света, но светящий в заданном направлении в виде направленного конуса (фонарик).
  • Свет в Direct3D состоит из трех составляющих: рассеянного, окружающего, зеркального, которые независимо друг от друга принимают участие в вычислении освещения граней. Они напрямую взаимодействуют с тремя цветами, определенными в свойствах материала. Результат такого сочетания (взаимодействия) и есть конечный цвет освещения сцены. В сценах Direct3D могут присутствовать до восьми различных по свойствам источников света. Для инициализации источника света в Direct3D необходимо проделать следующие шаги:

  • Объявить переменную типа D3DLIGHT9 ;
  • Указать тип источника и заполнить необходимые поля данной структуры;
  • Включить (разрешить) освещенность в сцене;
  • Установить источники света;
  • Включить источники света.
  • Структура D3DLIGHT9 содержит следующие поля:

    C++
    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; {1/(A0+A1*D+A2*D^2),D–расстояние от источника}
      float Theta;  {внутренний угол источника света}
      float Phi;    {внешний угол, [0…pi]}
    } D3DLIGHT9;
    Pascal
    TD3DLight9 = packed record
     _Type: TD3DLightType;     {тип источника света}
     Diffuse: TD3DColorValue;  {рассеянный свет, излучающий источником}
     Specular: TD3DColorValue; {зеркальный свет, излучающий источником}
     Ambient: TD3DColorValue;  {окружающий свет, излучающий источником}
     Position: TD3DVector;  {положение источника в сцене} для точечного
     Direction: TD3DVector;    {направление падающего света (вектор)}
     Range: Single;    {максимальное расстояние освещения} для точечного
     Falloff: Single;     {используется в прожекторном источнике}
     Attenuation0: Single;  {определяют закон изменения освещения}
     Attenuation1: Single;  {от параметра расстояния}
     Attenuation2: Single; {1/(A0+A1*D+A2*D^2),D–расстояние от источника}
     Theta: Single;          {внутренний угол источника света}
     Phi: Single;            {внешний угол, [0…pi]}
    end;

    Тип источника задается указанием одной из трех констант:

    D3DLIGHT_POINT, // точечный источник
    D3DLIGHT_SPOT,  // источник-прожектор
    D3DLIGHT_DIRECTIONAL. // направленный источник

    Ниже приведен пример установки направленного источника освещения в сцене.

    C++
    // создание и заполнение полей структуры, 
       установка типа источника
    D3DLIGHT9 light;
    ZeroMemory(light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_DIRECTIONAL;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Direction = D3DXVECTOR3(0.0f, 0.0f,1.0f);
    ...
    // разрешаем работы с освещенностью
    device->SetRenderState( D3DRS_LIGHTING, TRUE );
    ...
    // установка и включение первого (и единственного)
       источника света device->SetLight( 0, light );
    device->LightEnable( 0, TRUE );
    Pascal
    var  light: TD3DLight9;
    ...
    // создание и заполнение полей структуры, 
       установка типа источника
    ZeroMemory(@light,SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_DIRECTIONAL;
    light.Diffuse:=D3DXColor(1,0,0,0);
    light.Direction:=D3DXVector3(0,0,1);
    
    // разрешаем работы с освещенностью
    device.SetRenderState(D3DRS_LIGHTING, 1);
    ...
    
    // установка и включение первого (и единственного) 
       источника света
    device.SetLight(0,light);
    device.LightEnable(0,true);

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

    Именно с помощью нормалей рассчитывается освещенность объекта (граней). Как правило, нормали задаются в каждой вершине примитива.

    Поэтому необходимо корректно изменить формат вершин и набор FVF флагов.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        FLOAT nx, ny, nz;
    };
    VERTEX3D data[];
    
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_NORMAL);
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          nx, ny, nz: Single;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_NORMAL;
    
    var 
      data: array of Vertex3D;

    Следует заметить, что для правильной освещенности граней объектов все вектора, участвующие в расчете освещенности, должны быть нормированы (длина вектора равна единице). Библиотека Direct3D располагает функцией D3DXVec3Normalize(), которая позволяет привести вектор к единичной длине.

    C++
    D3DXVECTOR3 direction = D3DXVECTOR3(0.0f, 0.0f,1.0f);
    D3DXVec3Normalize( (D3DXVECTOR3*)light.Direction, direction );
    Pascal
    D3DXVec3Normalize( light.Direction, D3DXVector3(0, 0, 1) );

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

    C++ device->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE );
    Pascal device.SetRenderState(D3DRS_NORMALIZENORMALS, 1 );

    Для того, чтобы источник в сцене был активирован нужно проделать следующие шаги. Сначала нужно вызвать процедуру установки (регистрации) нужного источника света. Это реализуется через вызов метода SetLight() интерфейса IDirect3DDevice9. Данный метод имеет два параметра: номер источника (лампы), и второй – указатель на переменную типа D3DLIGHT9. Второй шаг состоит в вызове метода LightEnable() интерфейса IDirect3DDevice9 для включения/выключения нужного источника света. Данный метод имеет два параметра: номер источника и второй – булевская переменная (истина- включение источника, ложь - выключение).

    Ниже приведен пример освещения направленным источником света треугольной грани, у которой нормали в каждой вершине одинаковы. При этом положение камеры (наблюдателя) задано точкой (2,2,-2), а направление лучей света – (-2,-2,2).

    Как видно в каждом положении грань освещена одинаково (нормали в каждой вершине равны (0, 0, -1) ). Изменив хотя бы одну нормаль вершины, можно добиться того, что грань будет освещаться уже неравномерно. Пусть одна из вершин будет теперь иметь нормаль (1,0,-1). Результат освещенности грани при таких изменениях нормали показан ниже.

    Ниже приведены примеры заполнения "нужных" полей для точечного и прожекторного источников света.

    Пример для точечного источника света.

    C++
    D3DLIGHT9 light;
    ZeroMemory( light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_POINT;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f);
    light.Attenuation0 = 1.0f;
    light.Range = 100;
    Pascal
    var  light: TD3DLight9;
    ...
    ZeroMemory(@light, SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_POINT;
    light.Diffuse:=D3DXColor(1,1,0,0);
    light.Position:=D3DXVector3(2,2,-2);
    light.Attenuation0:=1;
    light.Range:=100;

    Пример для прожекторного источника света.

    C++
    D3DLIGHT9 light;
    ZeroMemory( light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_SPOT;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f);
    D3DXVECTOR3 direction = D3DXVECTOR3(-2.0f, -2.0f, 2.0f);
    D3DXVec3Normalize( (D3DXVECTOR3*)light.Direction, direction );
    light.Attenuation0 = 1.0f;
    light.Range = 100;
    light.Phi = D3DX_PI/2;
    light.Theta = D3DX_PI /3;
    light.Falloff = 1.0f;
    Pascal
    var  light: TD3DLight9;
    ...
    ZeroMemory(@light, SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_SPOT;
    light.Diffuse:=D3DXColor(1,1,0,0);
    light.Position:=D3DXVector3(2,2,-2);
    D3DXVec3Normalize(light.Direction, D3DXVector3(-2,-2,2));
    light.Attenuation0:=1;
    light.Range:=100;
    light.Phi:=pi/2;
    light.Theta:=pi/3;
    light.Falloff:=1;

    Построение стандартных объектов

    Библиотека Direct3D располагает рядом встроенных функций для построения простых стандартных трехмерных примитивов (куб, цилиндр, сфера, тор):

  • D3DXCreatePolygon // полигон
  • D3DXCreateBox // параллелограмм
  • D3DXCreateCylinder // цилиндр
  • D3DXCreateSphere // сфера
  • D3DXCreateTorus // тор
  • D3DXCreateTeapot // чайник
  • При создании объектов таким способом вершины "получают" формат, в котором присутствует положение (D3DFVF_XYZ) и нормаль (D3DFVF_NORMAL). Существует возможность задавать уровень детализации при создании перечисленных выше трехмерных примитивов. Следует заметить, что нормаль к каждой вершине созданного объекта вычисляется автоматически. Рассмотрим их функции создания и визуализации. Для работы с подобными примитивами необходимо объявить переменную интерфейсного типа ID3DXMesh, в которой и будет "храниться" трехмерный объект. Визуализация созданного объекта осуществляется вызовом метода DrawSubset интерфейса ID3DXMesh.

    Функция создания полигона (правильного многоугольника):

    D3DXCreatePolygon(
    ссылка на устройство вывода,
    длина стороны полигона,
    количество сторон полигона,
    результат,
    указатель на смежные треугольники);

    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.

    C++
    // объявление переменной
    LPD3DXMESH polygon;
    // создание объекта полигон
    D3DXCreatePolygon( device, 0.5, 10, polygon, NULL);
    // визуализация
    polygon->DrawSubset(0);
    Pascal
    // объявление переменной
    var polygon: ID3DXMesh; 
    // создание объекта полигон
    D3DXCreatePolygon(device, 0.5, 10, polygon, nil);
    // визуализация
    polygon.DrawSubset(0);

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

    Длина стороны = 0.5

    Количество сторон = 10

    Длина стороны = 2

    Количество сторон = 3

    Длина стороны = 0.1

    Количество сторон = 36

    Функция вывода параллелепипеда

    D3DXCreateBox(
    ссылка на устройство вывода,
    ширина,
    высота,
    глубина,
    результат,
    указатель на смежные треугольники);

    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.

    C++
    // объявление переменной
    LPD3DXMESH box;
    // создание объекта 
    D3DXCreateBox( device, 1.0f, 0.5f, 2.0f, box, NULL);
    // визуализация
    box->DrawSubset(0);
    Pascal
    // объявление переменной
    var box: ID3DXMesh; 
    // создание объекта 
    D3DXCreateBox(device, 1, 0.5, 2, box, nil);
    // визуализация
    box.DrawSubset(0);

    Примеры вывода параллелограмма с различными длинами сторон

    ширина = 1

    высота = 0.5

    глубина = 2

    ширина = 1

    высота = 1

    глубина = 1

    ширина = 0.5

    высота = 2

    глубина = 1

    Функция построения цилиндра

    D3DXCreateCylinder(
    ссылка на устройство
    радиус первого основания
    радиус второго основания
    высота цилиндра
    количество разбиений "по радиусу"
    количество разбиений "по длине"
    результат
    указатель на смежные треугольники);
    C++
    LPD3DXMESH cylinder;
    D3DXCreateCylinder(device, 0.2f, 0.2f, 1, 16, 3, 
    cylinder, NULL);
    
    cylinder->DrawSubset(0);
    Pascal
    var cylinder: ID3DXMesh;
    D3DXCreateCylinder(device, 0.2, 0.2, 1, 16, 3, cylinder, nil);            
    
    cylinder.DrawSubset(0);
    Первое основание = 0.2

    Второе основание = 0.2

    Разбиений по радиусу = 16

    Первое основание = 0.4

    Второе основание = 0.2

    Разбиений по радиусу = 16

    Первое основание = 0.4

    Второе основание = 0

    Разбиений по радиусу = 6

    Функция построения сферы:

    D3DXCreateSphere(
    ссылка на устройство,
    радиус сферы,
    разбиений "по радиусу", // число апельсиновых долек
    разбиений "вдоль", // 
    результат,
    указатель на смежные треугольники);
    Разбиений по радиусу = 6, вдоль = 32 Разбиений по радиусу = 32, вдоль = 6
    C++
    LPD3DXMESH sphere;
    D3DXCreateSphere(device, 1.0f, 16, 16, sphere, NULL);
    
    sphere->DrawSubset(0);
    Pascal
    var sphere: ID3DXMesh;
    D3DXCreateSphere(device, 1, 16, 16, sphere, nil);            
    
    sphere.DrawSubset(0);

    Функция построения тора:

    D3DXCreateTorus(
    ссылка на устройство,
    внутренний радиус тора,
    внешний радиус тора,
    количество разбиений в поперечном сечении,
    количество разбиений по кругу,
    результат,
    указатель на смежные треугольники);
    Разбиений в сечение = 6

    Разбиений по кругу = 32

    Разбиений в сечение = 32

    Разбиений по кругу = 6

    C++
    LPD3DXMESH torus;
    D3DXCreateTorus(device, 0.2f, 0.8f, 32, 6, torus, NULL);
    torus->DrawSubset(0);
    Pascal
    var torus: ID3DXMesh;
    D3DXCreateTorus(device, 0.2, 0.8, 32, 6, torus, nil);
    
    torus.DrawSubset(0);

    Функция построения чайника:

    D3DXCreateTeapot(
    ссылка на устройство,
    результат,
    указатель на смежные треугольники);

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

    C++
    LPD3DXMESH teapot;
    D3DXCreateTeapot(device, teapot, NULL);
    teapot->DrawSubset(0);
    Pascal
    var teapot: ID3DXMesh;
    D3DXCreateTeapot(device, teapot, nil);
    
    teapot.DrawSubset(0);

    Ниже приведены примеры освещения трехмерного объекта точечным источником света с различными характеристиками.

    light.Ambient = D3DXCOLOR(0.3f, 0,0,0);
    light.Diffuse = D3DXCOLOR(0.5f, 0.0, 0,0);
    light.Ambient = D3DXCOLOR(0.2f, 0,0,0);
    light.Diffuse = D3DXCOLOR(0.5, 0.0, 0,0);
    light.Ambient = D3DXCOLOR(0.2, 0,0,0);
    light.Specular = D3DXCOLOR(0.3,0,0.0,0);

    Следует заметить, что зеркальное освещение требует больших вычислений, чем другие типы освещения, поэтому библиотека Direct3DM предоставляет возможность отключать его (по умолчанию оно выключено). Для включения зеркального освещения необходимо установить константу состояния D3DRS_SPECULARENABLE в значение "истина".

    C++ device->SetRenderState( D3DRS_SPECULARENABLE, TRUE );
    Pascal device.SetRenderState(D3DRS_SPECULARENABLE, 1);

    Как мы уже говорили, библиотека Direct3D позволяет одновременно использовать (обсчитывать) в сцене до восьми источников света. Программно использование нескольких источников света в сцене накладывает на программиста дополнительные "обязанности" по созданию и заполнению полей структуры для каждого источника. Ниже приведен пример использования трех разноцветных точечных источников света в сцене, которые расположены на координатных осях системы координат.

    C++
    D3DMATERIAL9 material;
    D3DLIGHT9 light0, light1, light2;
    
    ZeroMemory( material, sizeof(D3DMATERIAL9) );
    material.Diffuse=D3DXCOLOR(1.0f, 1.0f, 1.0f, 0.0f);
    
    ZeroMemory( light0, sizeof(D3DLIGHT9) );
    light0.Type = D3DLIGHT_POINT;
    light0.Diffuse = D3DXCOLOR(0.5f, 0.0f, 0.0f, 0.0f);
    light0.Position = D3DXVECTOR3(4.0f, 0.0f, 0.0f);
    …
    ZeroMemory( light1, sizeof(D3DLIGHT9) );
    light1.Type = D3DLIGHT_POINT;
    light1.Diffuse = D3DXCOLOR(0.0f, 0.5f, 0.0f, 0.0f);
    light1.Position = D3DXVECTOR3(0.0f, 0.0f, -4.0f);
    …
    ZeroMemory( light2, sizeof(D3DLIGHT9) );
    light2.Type = D3DLIGHT_POINT;
    light2.Diffuse = D3DXCOLOR(0.0f, 0.0f, 0.5f, 0.0f);
    light2.Position = D3DXVECTOR3(-4.0f, 0.0f, 0.0f);
    
    device->SetRenderState( D3DRS_LIGHTING, TRUE );
    ...
    // процедура рендеринга
    device->SetLight( 0, light0 );
    device->LightEnable( 0, TRUE );
    device->SetLight( 1, light1 );
    device->LightEnable( 1, TRUE );
    device->SetLight( 2, light2 );
    device->LightEnable( 2, TRUE );
    Pascal
    var
      material: TD3DMaterial9;
      light0, light1, light2: TD3DLight9;
    
    ZeroMemory(@material,SizeOf(TD3DMaterial9));
    material.Diffuse:=D3DXColor(1,1,1,0);
    
    light0._Type:=D3DLIGHT_POINT;
    light0.Diffuse:=D3DXColor(0.5, 0.0, 0, 0);
    light0.Position:=D3DXVector3(4,0,0);
    …
    light1._Type:=D3DLIGHT_POINT;
    light1.Diffuse:=D3DXColor(0.0, 0.5, 0, 0);
    light1.Position:=D3DXVector3(0,0,-4);
    …
    light2._Type:=D3DLIGHT_POINT;
    light2.Diffuse:=D3DXColor(0.0, 0.0, 0.5, 0);
    light2.Position:=D3DXVector3(-4,0,0);
    …
    device.SetRenderState(D3DRS_LIGHTING, 1);
    
    // процедура рендеринга
    device.SetLight(0,light0);
    device.LightEnable(0,true);
    device.SetLight(1,light1);
    device.LightEnable(1,true);
    device.SetLight(2,light2);
    device.LightEnable(2,true);

    Сложные (содержащие десятки тысяч вершин) объекты, как правило, создаются в профессиональных трехмерных редакторах (3D Studio, Maya, LightWave, и т.п.). Библиотека Direct3D содержит средства для загрузки и визуализации моделей, содержащихся в x-файлах. Х-файл – файл, содержащий в себе данные о трехмерном объекте (координаты вершин, материалы, нормали, текстуры и анимацию). Вначале необходимо объявить нужные переменные.

    C++ LPD3DXMESH mesh;
    Pascal var mesh: ID3DXMesh;

    Для загрузки трехмерного объекта (модели) из Х-файла предназначена функция

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

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

    C++
    D3DXLoadMeshFromX("ship.x", D3DXMESH_MANAGED, 
                      device, NULL, NULL, NULL, NULL, mesh);
    Pascal
    D3DXLoadMeshFromX('ship.x', D3DXMESH_MANAGED, 
                      device, nil, nil, nil, nil, mesh);

    Для визуализации трехмерной модели из Х-файла необходимо вызвать метод DrawSubset интерфейса ID3DXMesh, где в качестве параметра передается номер выводимой группы (подмножества) модели. Если модель не разбита на группы, то параметр метода равен нулю.

    C++ mesh->DrawSubset(0);
    Pascal mesh.DrawSubset(0);

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

    Реалистичные построения

    Принцип текстурирования трехмерных объектов ничем не отличается от двумерного аналога. Для каждой треугольной грани необходимо задать текстурные координаты. Единственное отличие от двумерного варианта заключается в формате вершин и наборе FVF флагов.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        FLOAT u, v;
    };
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_TEX1);
    ...
    VERTEX3D points[3] = {
            { -1.0f,-1.0f, 0.0f, 0.0f, 1.0f, },
            {  0.0f, 1.0f, 0.0f, 0.5f, 0.0f, },
            {  1.0f,-1.0f, 0.0f, 1.0f, 1.0f, }
    }
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          u, v: Single;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_TEX1;
    
    var 
      points: array [0..2] of Vertex3D = (
       (x: -1; y: -1; z: 0; u: 0;   v: 1),
       (x:  0; y:  1; z: 0; u: 0.5; v: 0),
       (x:  1; y: -1; z: 0; u: 1;   v: 1)
      );

    Ниже показан пример вывода куба, у которого все грани покрыты различными текстурами. Для этого можно объявить массив для хранения текстур из шести элементов типа IDirect3DTexture9.

    C++
    LPDIRECT3DTEXTURE9 textures[6];
    LPCSTR files[] = {
      {"bricks.bmp"}, {"colors.bmp"}, {"Lake.bmp"},
      {"stone_wall.bmp"}, {"wall.bmp"}, {"fur.jpg"} };
    ...
    for (int i=0; i<6; i++) 
      D3DXCreateTextureFromFile( device, files[i], textures[i] );
    Pascal
    var
      textures: array [0..5] of IDirect3DTexture9;
      files: array [0..5] of AnsiString = (
      ('bricks.bmp'), ('colors.bmp'), ('Lake.bmp'),
      ('stone_wall.bmp'), ('wall.bmp'), ('fur.jpg') );
    ...
    for i:=0 to 5 do
      D3DXCreateTextureFromFile(device, PChar(files[i]), textures[i]);

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

    C++
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    for i:=0 to 5 do begin
      device.SetTexture(0, textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

    Ниже приведены примеры (кадры анимации) вращения такого куба.

    Рассмотрим один из способов построения полупрозрачных трехмерных объектов. Данный метод прозрачности применим только для выпуклых объектов. Идея его заключается в следующем. Вначале выводятся те грани объекта, которые считаются невидимыми для наблюдателя (задние грани). Затем включается режим полупрозрачности (смешивания цветов). Выводим грани объекта, которые видны наблюдателю. Отображение "передних" и "задних" для наблюдателя сторон объекта можно осуществить с помощью механизма отсечения граней. Предположим, что вершины грани перечислены по часовой стрелке. Тогда отключение вывода таких граней отобразит нам "задние" стороны примитива (в нашем случае куба). Программно этот шаг может выглядеть так.

    C++
    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
    for i:=0 to 5 do begin
      device.SetTexture(0,textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

    Ниже показан пример вывода "внутренних" сторон куба.

    Включение полупрозрачности с нужными параметрами смешивания цветов.

    C++
    device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR );
    device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR );
    Pascal
    device.SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
    device.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
    device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);

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

    C++
    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
    for i:=0 to 5 do begin
      device.SetTexture(0,textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

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

    Для придания трехмерной сцене еще большей реалистичности можно добавить тени, которые отбрасывают объекты при освещении их некоторым источником света. Алгоритмы затенения в случае точечных источников света схожи с алгоритмами удаления невидимых поверхностей, с единственным лишь отличием. В алгоритмах удаления невидимых граней определяются поверхности, которые можно увидеть из точки положения наблюдателя, а в алгоритмах затенения выделяются поверхности, которые можно "увидеть" из местоположения источника света. Грани, видимые как из точки наблюдения, так и из источника света, не лежат в тени. Те же поверхности, которые видимы из точки наблюдения, но не видимы из источника света, находятся в тени.

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

  • Создать луч, начинающийся в источнике света, проходящий через вершину полигона и пересекающий плоскость проецирования;
  • Получить (найти) точку пересечения этого луча с плоскостью;
  • Проделать шаги 1 и 2 для всех вершин грани, получив таким образом координаты "теневого" полигона и затем отрисовать его обычными методами.
  • Пусть у нас источник света находится в точке L=(Lx,Ly,Lz), треугольная грань объекта имеет координаты вершин P=(Px,Py,Pz), Q=(Qx,Qy,Qz), R=(Rx,Ry,Rz), а плоскость проецирования (затенения) $$\alpha$$ задается неявным уравнением: Ax+By+Cz+D=0. Требуется определить координаты проекций вершин P,Q,R на плоскость $$\alpha.$$ Обозначим через M=(Mx,My,Mz),N=(Nx,Ny,Nz),O=(Ox,Oy,Oz) - проекции точек P,Q,R соответственно на плоскость $$\alpha.$$ Найдем координаты точки M. Так как точка M лежит на луче, то будет справедливы следующие уравнения$$\left\{ \begin{array}{ccc} Mx=Lx+t(Px-Lx) \\ My=Ly+t(Py-Ly) \\ Mz=Lz+t(Pz-Lz) \end{array} \right,$$ для некоторого параметра t.

    С другой стороны точка M лежит в плоскости $$\alpha,$$ поэтому верно уравнение: A*Mx+B*My+C*Mz+D=0.

    Подставим выражения для Mx,My,Mz в уравнение плоскости: A*(Lx+t(Px-Lx))+B*(Ly+t(Py-Ly))+C*(Lz+t(Pz-Lz))+D=0. Это линейное уравнение относительно параметра t, решая которое находим, что$$t^{.}=-\frac{A*Lx+B*Ly+C*Lz+D}{A(Px-Lx)+B(Py-Ly)+C(Pz-Lz)}.$$ Если $$t^{.} 0$$, то координаты точки пересечения луча и плоскости вычисляются по следующим формулам:$$\left\{ \begin{array}{ccc} Mx=Lx+t^{.}(Px-Lx) \\ My=Ly+t^{.}(Py-Ly) \\ Mz=Lz+t^{.}(Pz-Lz) \end{array}\right.$$

    Аналогичные шаги проделываем для двух других вершин полигона Q и R. Следует заметить, что при вычислении тени на плоскость y=0, параметр $$t^{.}=\frac{Ly}{Ly-Py}$$.

    Рассмотренный метод имеет несколько "подводных камней". Тень будет прорисовываться даже в том случае, если источник света находится между полигоном и плоскостью проецирования или же источник света расположен вообще по другую сторону от плоскости. Поэтому такие ситуации необходимо отслеживать и корректно обрабатывать.

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

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

    Разберем построение трехмерной поверхности, представляющей собой функцию двух переменных y=f(x,z). Пусть для простоты у нас область определения функции f(x,z) ограничена единичным квадратом, т.е. 0<=x<=1, 0<=z<=1. Визуализация рассмотренных поверхностей на растровых устройствах может быть осуществлена путем кусочно-линейной аппроксимации треугольными гранями. Такая триангуляционная поверхность может быть получена следующим образом. Параметрическое пространство x,z (единичный квадрат) дискретизируется по координате x с шагом dx, по координате z – с шагом dz. Для каждой пары значений (x,z) вычисляется точка на поверхности y=f(x,z). Полученный таким образом массив точек триангулируется регулярной сеткой.

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

    Разберем один из способов построения ландшафтов, который базируется на так называемой карте высот. Карта высот представляет собой двумерный массив, где каждый элемент (пиксель) определяет высоту точки в сетке треугольников. Как правило, значение высоты ранжируется в диапазоне от 0 до 255. Поэтому для хранения подобных карт высот используют изображения в оттенках серого цвета ( grayscale image ). Более светлые участки на таких изображениях соответствуют более высоким значениям точек, а темные участки определяют низкие значения высот. Кроме этого подбирается текстура, схожая по "характеру" с ландшафтом. Ниже приведен пример построения ландшафта по карте высот.

    Страницы:

    Освещенность и материалы

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

    При расчете освещенности грани в компьютерной графике учитывают следующие типы отражения света от поверхности: рассеянное, диффузное, зеркальное. Интенсивность освещения граней объектов рассеянным светом считается постоянной в любой точке пространства. Это обусловлено тем, что рассеянный свет создается многочисленными отражениями от различных поверхностей пространства. Такой свет практически всегда присутствует в реальной обстановке. Даже если объект защищен от прямых лучей, исходящих от точечного источника света, он все равно будет виден из-за наличия рассеянного света. Интенсивность рассеянного света выражается как $$I=I_{a}k_{a}$$, где $$I_{a}$$ - интенсивность рассеянного света, $$k_{a} \in [0,1]$$ - коэффициент рассеянного отражения, зависящий от отражательных свойств материала (поверхности). При освещении объекта только рассеянным светом, все его грани будут закрашены одинаково, а общие ребра будут неразличимы.

    При наличии в сцене точечного источника света, интенсивность диффузного отражения пропорциональна косинусу угла между нормалью к поверхности и направлением на источник света. В этом случае для вычисления интенсивности диффузного отражения применяют закон косинусов Ламберта: $$I=I_{p}k_{d}cos \theta$$, где $$I_{p}$$ - интенсивность точечного источника света, $$k_{d} \in [0,1]$$ - коэффициент диффузного отражения, $$\theta$$ - угол падения, рассчитываемый как угол между направлением на источник света и нормалью к поверхности. Если вектора N, L нормализованы, т.е. |N|=|L|=1, то $$\cos \theta =(\overrightarrow N \overrightarrow L)$$ - скалярное произведение векторов, и уравнение можно записать в виде $$I_p k_d (\overline N \cdot \overline L)$$.

    Свойством диффузного отражения является равномерность по всем направлениям отраженного света. Поэтому такие объекты имеют одинаковую яркость, вне зависимости от угла обзора. К примеру, матовые поверхности обладают таким свойством. Предположим, что у нас имеется два полигона, одинаково ориентированы относительно источника света, но расположенные на разных расстояниях от него. Если вычислить значения интенсивностей освещенности для каждого полигона, по приведенной формуле, то они окажутся одинаковыми. Это означает, что объекты, расположенные на разном расстоянии от источника света, будут освещаться независимо от их местоположения в трехмерной сцене. В этом случае интенсивность света должна быть обратнопропорциональна квадрату расстояния от источника до грани объекта. Тогда объект, расположенный дальше от источника будет темнее. Тем не менее на практике, как правило, используют не квадрат расстояния, а линейное затухание. В этом случае формула общей интенсивности может быть записана как:$$I=I_ak_a+\frac{I_p k_d (\overline N \cdot \overline L)}{d+k},$$ где d - расстояние от центра проекции (наблюдателя) до поверхности, k - некоторая константа, k>=1.

    Зеркальное отражение можно получить от любой блестящей поверхности. Осветите ярким светом обычное яблоко, и световой блик возникнет в результате зеркального отражения, а свет, отраженный от остальной части яблока, появится вследствие диффузного отражения. Следует отметить, что в том месте, где находится световой блик, яблоко будет казаться не красным, а, скорее всего – белым, т.е. окрашенным в цвет падающего света. Если изменить положение наблюдателя, то световой блик тоже сместится. Это объясняется тем, что блестящие поверхности отражают свет неодинаково по всем направлениям. От идеально зеркальной поверхности свет отражается только в том направлении, для которого углы падения и отражения совпадают. Зеркально отраженный свет можно будет увидеть, если угол между вектором отражения (на рисунке обозначен R ) и вектором наблюдения (на рисунке обозначен S ) равен нулю.

    Для неидеальных отражающих поверхностей интенсивность отраженного света резко падает с ростом $$\alpha.$$ В модели, предложенной Фонгом, быстрое убывание интенсивности света, описывается функцией $$cos^{n}\alpha$$, где $$n \in [1,200]$$ в зависимости от вида поверхности. Для идеальной отражающей поверхности $$n\to\infty$$. Интенсивность зеркального отражения света по модели Фонга записывается как $$I=I_{p}*k_{S}*cos^{n} \alpha$$, где $$I_{p}$$ - интенсивность источника света, $$k_{S} \in [0,1]$$ - коэффициент зеркального отражения. Если вектора R и S нормированы, то формула может быть преобразована к виду:$$I=I_{pks} (\overline R \cdot \overline S)^n.$$ Интенсивность зеркального света обратно пропорциональна расстоянию от источника до грани. Для практических задач здесь используют также модель линейного затухания света, которая выражается следующей формулой:$$I=\frac{I_{pks} (\overline R \cdot \overline S)}{d+k}^n,$$ где d - расстояние от наблюдателя до поверхности, k - некоторая константа.

    Таким образом, интенсивность грани складывается из трех составляющих: рассеянного света, диффузного света и зеркального света. Формула расчета общей интенсивности с учетом расстояния от наблюдателя до освещенной грани и трех составляющих света записывается как$$I=I_ak_a+\frac{I_p}{d+k}(k_d (\overline L \cdot \overline N)+k_s (\overline R \cdot \overline S)^n).$$

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

    Для работы с материалом предусмотрен специальный тип данных D3DMATERIAL9, который содержит следующие поля.

    C++
    typedef struct _D3DMATERIAL9 {
      D3DCOLORVALUE Diffuse;   {рассеянный свет, исходящий от материала}
      D3DCOLORVALUE Ambient;   {определяет окружающий свет}
      D3DCOLORVALUE Specular;  {определяет отражающий (зеркальный) свет}
      D3DCOLORVALUE Emissive;  {определяет излучающий свет материала}
      float Power;             {мощность отражения}
    } D3DMATERIAL9;
    Pascal
    TD3DMaterial9 = packed record
     Diffuse: TD3DColorValue;  {рассеянный свет, исходящий от материала}
     Ambient: TD3DColorValue;  {определяет окружающий свет}
     Specular: TD3DColorValue; {определяет отражающий (зеркальный) свет}
     Emissive: TD3DColorValue; {определяет излучающий свет материала}
     Power: Single;            {мощность отражения}
    end;

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

  • Объявление переменной типа D3DMATERIAL9 ;
  • Заполнение соответствующих полей данной структуры;
  • Установка материала.
  • Программно эти шаги реализуются таким образом:

    C++
    D3DMATERIAL9 material;
    ZeroMemory( material, sizeof(D3DMATERIAL9) );
    material.Diffuse=D3DXCOLOR(0.7f, 0.0f, 0.0f, 0.0f);
    material.Ambient=D3DXCOLOR(0.2f, 0.0f, 0.0f, 0.0f);
    material.Specular=D3DXCOLOR(0.1f, 0.0f, 0.0f, 0.0f);
    ...
    device->SetMaterial( material );
    Pascal
    var 
       material: TD3DMaterial9;
    ...
    ZeroMemory(@material,SizeOf(TD3DMaterial9));
    material.Diffuse:=D3DXColor(0.7, 0, 0, 0); // r, g, b, a
    material.Ambient:=D3DXColor(0.2, 0, 0, 0);
    material.Specular:=D3DXColor(0.1, 0, 0, 0);
    ...
    device.SetMaterial(material);

    Метод SetMaterial вызывается при выводе объектов (в процедуре рендеринга). Кроме определения свойств материала необходимо задать источники света. Библиотека Direct3D поддерживает три типа источника света:

  • Параллельный (направленный) – этот тип не имеет определенного источника света, он как бы повсюду, но светит в одном направлении.
  • Точечный – источник, светящий во всех направлениях (лампочка).
  • Прожекторный (нацеленный) – тип, имеющий определенный источник света, но светящий в заданном направлении в виде направленного конуса (фонарик).
  • Свет в Direct3D состоит из трех составляющих: рассеянного, окружающего, зеркального, которые независимо друг от друга принимают участие в вычислении освещения граней. Они напрямую взаимодействуют с тремя цветами, определенными в свойствах материала. Результат такого сочетания (взаимодействия) и есть конечный цвет освещения сцены. В сценах Direct3D могут присутствовать до восьми различных по свойствам источников света. Для инициализации источника света в Direct3D необходимо проделать следующие шаги:

  • Объявить переменную типа D3DLIGHT9 ;
  • Указать тип источника и заполнить необходимые поля данной структуры;
  • Включить (разрешить) освещенность в сцене;
  • Установить источники света;
  • Включить источники света.
  • Структура D3DLIGHT9 содержит следующие поля:

    C++
    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; {1/(A0+A1*D+A2*D^2),D–расстояние от источника}
      float Theta;  {внутренний угол источника света}
      float Phi;    {внешний угол, [0…pi]}
    } D3DLIGHT9;
    Pascal
    TD3DLight9 = packed record
     _Type: TD3DLightType;     {тип источника света}
     Diffuse: TD3DColorValue;  {рассеянный свет, излучающий источником}
     Specular: TD3DColorValue; {зеркальный свет, излучающий источником}
     Ambient: TD3DColorValue;  {окружающий свет, излучающий источником}
     Position: TD3DVector;  {положение источника в сцене} для точечного
     Direction: TD3DVector;    {направление падающего света (вектор)}
     Range: Single;    {максимальное расстояние освещения} для точечного
     Falloff: Single;     {используется в прожекторном источнике}
     Attenuation0: Single;  {определяют закон изменения освещения}
     Attenuation1: Single;  {от параметра расстояния}
     Attenuation2: Single; {1/(A0+A1*D+A2*D^2),D–расстояние от источника}
     Theta: Single;          {внутренний угол источника света}
     Phi: Single;            {внешний угол, [0…pi]}
    end;

    Тип источника задается указанием одной из трех констант:

    D3DLIGHT_POINT, // точечный источник
    D3DLIGHT_SPOT,  // источник-прожектор
    D3DLIGHT_DIRECTIONAL. // направленный источник

    Ниже приведен пример установки направленного источника освещения в сцене.

    C++
    // создание и заполнение полей структуры, 
       установка типа источника
    D3DLIGHT9 light;
    ZeroMemory(light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_DIRECTIONAL;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Direction = D3DXVECTOR3(0.0f, 0.0f,1.0f);
    ...
    // разрешаем работы с освещенностью
    device->SetRenderState( D3DRS_LIGHTING, TRUE );
    ...
    // установка и включение первого (и единственного)
       источника света device->SetLight( 0, light );
    device->LightEnable( 0, TRUE );
    Pascal
    var  light: TD3DLight9;
    ...
    // создание и заполнение полей структуры, 
       установка типа источника
    ZeroMemory(@light,SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_DIRECTIONAL;
    light.Diffuse:=D3DXColor(1,0,0,0);
    light.Direction:=D3DXVector3(0,0,1);
    
    // разрешаем работы с освещенностью
    device.SetRenderState(D3DRS_LIGHTING, 1);
    ...
    
    // установка и включение первого (и единственного) 
       источника света
    device.SetLight(0,light);
    device.LightEnable(0,true);

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

    Именно с помощью нормалей рассчитывается освещенность объекта (граней). Как правило, нормали задаются в каждой вершине примитива.

    Поэтому необходимо корректно изменить формат вершин и набор FVF флагов.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        FLOAT nx, ny, nz;
    };
    VERTEX3D data[];
    
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_NORMAL);
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          nx, ny, nz: Single;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_NORMAL;
    
    var 
      data: array of Vertex3D;

    Следует заметить, что для правильной освещенности граней объектов все вектора, участвующие в расчете освещенности, должны быть нормированы (длина вектора равна единице). Библиотека Direct3D располагает функцией D3DXVec3Normalize(), которая позволяет привести вектор к единичной длине.

    C++
    D3DXVECTOR3 direction = D3DXVECTOR3(0.0f, 0.0f,1.0f);
    D3DXVec3Normalize( (D3DXVECTOR3*)light.Direction, direction );
    Pascal
    D3DXVec3Normalize( light.Direction, D3DXVector3(0, 0, 1) );

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

    C++ device->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE );
    Pascal device.SetRenderState(D3DRS_NORMALIZENORMALS, 1 );

    Для того, чтобы источник в сцене был активирован нужно проделать следующие шаги. Сначала нужно вызвать процедуру установки (регистрации) нужного источника света. Это реализуется через вызов метода SetLight() интерфейса IDirect3DDevice9. Данный метод имеет два параметра: номер источника (лампы), и второй – указатель на переменную типа D3DLIGHT9. Второй шаг состоит в вызове метода LightEnable() интерфейса IDirect3DDevice9 для включения/выключения нужного источника света. Данный метод имеет два параметра: номер источника и второй – булевская переменная (истина- включение источника, ложь - выключение).

    Ниже приведен пример освещения направленным источником света треугольной грани, у которой нормали в каждой вершине одинаковы. При этом положение камеры (наблюдателя) задано точкой (2,2,-2), а направление лучей света – (-2,-2,2).

    Как видно в каждом положении грань освещена одинаково (нормали в каждой вершине равны (0, 0, -1) ). Изменив хотя бы одну нормаль вершины, можно добиться того, что грань будет освещаться уже неравномерно. Пусть одна из вершин будет теперь иметь нормаль (1,0,-1). Результат освещенности грани при таких изменениях нормали показан ниже.

    Ниже приведены примеры заполнения "нужных" полей для точечного и прожекторного источников света.

    Пример для точечного источника света.

    C++
    D3DLIGHT9 light;
    ZeroMemory( light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_POINT;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f);
    light.Attenuation0 = 1.0f;
    light.Range = 100;
    Pascal
    var  light: TD3DLight9;
    ...
    ZeroMemory(@light, SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_POINT;
    light.Diffuse:=D3DXColor(1,1,0,0);
    light.Position:=D3DXVector3(2,2,-2);
    light.Attenuation0:=1;
    light.Range:=100;

    Пример для прожекторного источника света.

    C++
    D3DLIGHT9 light;
    ZeroMemory( light, sizeof(D3DLIGHT9) );
    light.Type = D3DLIGHT_SPOT;
    light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f);
    light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f);
    D3DXVECTOR3 direction = D3DXVECTOR3(-2.0f, -2.0f, 2.0f);
    D3DXVec3Normalize( (D3DXVECTOR3*)light.Direction, direction );
    light.Attenuation0 = 1.0f;
    light.Range = 100;
    light.Phi = D3DX_PI/2;
    light.Theta = D3DX_PI /3;
    light.Falloff = 1.0f;
    Pascal
    var  light: TD3DLight9;
    ...
    ZeroMemory(@light, SizeOf(TD3DLight9));
    light._Type:=D3DLIGHT_SPOT;
    light.Diffuse:=D3DXColor(1,1,0,0);
    light.Position:=D3DXVector3(2,2,-2);
    D3DXVec3Normalize(light.Direction, D3DXVector3(-2,-2,2));
    light.Attenuation0:=1;
    light.Range:=100;
    light.Phi:=pi/2;
    light.Theta:=pi/3;
    light.Falloff:=1;

    Построение стандартных объектов

    Библиотека Direct3D располагает рядом встроенных функций для построения простых стандартных трехмерных примитивов (куб, цилиндр, сфера, тор):

  • D3DXCreatePolygon // полигон
  • D3DXCreateBox // параллелограмм
  • D3DXCreateCylinder // цилиндр
  • D3DXCreateSphere // сфера
  • D3DXCreateTorus // тор
  • D3DXCreateTeapot // чайник
  • При создании объектов таким способом вершины "получают" формат, в котором присутствует положение (D3DFVF_XYZ) и нормаль (D3DFVF_NORMAL). Существует возможность задавать уровень детализации при создании перечисленных выше трехмерных примитивов. Следует заметить, что нормаль к каждой вершине созданного объекта вычисляется автоматически. Рассмотрим их функции создания и визуализации. Для работы с подобными примитивами необходимо объявить переменную интерфейсного типа ID3DXMesh, в которой и будет "храниться" трехмерный объект. Визуализация созданного объекта осуществляется вызовом метода DrawSubset интерфейса ID3DXMesh.

    Функция создания полигона (правильного многоугольника):

    D3DXCreatePolygon(
    ссылка на устройство вывода,
    длина стороны полигона,
    количество сторон полигона,
    результат,
    указатель на смежные треугольники);

    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.

    C++
    // объявление переменной
    LPD3DXMESH polygon;
    // создание объекта полигон
    D3DXCreatePolygon( device, 0.5, 10, polygon, NULL);
    // визуализация
    polygon->DrawSubset(0);
    Pascal
    // объявление переменной
    var polygon: ID3DXMesh; 
    // создание объекта полигон
    D3DXCreatePolygon(device, 0.5, 10, polygon, nil);
    // визуализация
    polygon.DrawSubset(0);

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

    Длина стороны = 0.5

    Количество сторон = 10

    Длина стороны = 2

    Количество сторон = 3

    Длина стороны = 0.1

    Количество сторон = 36

    Функция вывода параллелепипеда

    D3DXCreateBox(
    ссылка на устройство вывода,
    ширина,
    высота,
    глубина,
    результат,
    указатель на смежные треугольники);

    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.

    C++
    // объявление переменной
    LPD3DXMESH box;
    // создание объекта 
    D3DXCreateBox( device, 1.0f, 0.5f, 2.0f, box, NULL);
    // визуализация
    box->DrawSubset(0);
    Pascal
    // объявление переменной
    var box: ID3DXMesh; 
    // создание объекта 
    D3DXCreateBox(device, 1, 0.5, 2, box, nil);
    // визуализация
    box.DrawSubset(0);

    Примеры вывода параллелограмма с различными длинами сторон

    ширина = 1

    высота = 0.5

    глубина = 2

    ширина = 1

    высота = 1

    глубина = 1

    ширина = 0.5

    высота = 2

    глубина = 1

    Функция построения цилиндра

    D3DXCreateCylinder(
    ссылка на устройство
    радиус первого основания
    радиус второго основания
    высота цилиндра
    количество разбиений "по радиусу"
    количество разбиений "по длине"
    результат
    указатель на смежные треугольники);
    C++
    LPD3DXMESH cylinder;
    D3DXCreateCylinder(device, 0.2f, 0.2f, 1, 16, 3, 
    cylinder, NULL);
    
    cylinder->DrawSubset(0);
    Pascal
    var cylinder: ID3DXMesh;
    D3DXCreateCylinder(device, 0.2, 0.2, 1, 16, 3, cylinder, nil);            
    
    cylinder.DrawSubset(0);
    Первое основание = 0.2

    Второе основание = 0.2

    Разбиений по радиусу = 16

    Первое основание = 0.4

    Второе основание = 0.2

    Разбиений по радиусу = 16

    Первое основание = 0.4

    Второе основание = 0

    Разбиений по радиусу = 6

    Функция построения сферы:

    D3DXCreateSphere(
    ссылка на устройство,
    радиус сферы,
    разбиений "по радиусу", // число апельсиновых долек
    разбиений "вдоль", // 
    результат,
    указатель на смежные треугольники);
    Разбиений по радиусу = 6, вдоль = 32 Разбиений по радиусу = 32, вдоль = 6
    C++
    LPD3DXMESH sphere;
    D3DXCreateSphere(device, 1.0f, 16, 16, sphere, NULL);
    
    sphere->DrawSubset(0);
    Pascal
    var sphere: ID3DXMesh;
    D3DXCreateSphere(device, 1, 16, 16, sphere, nil);            
    
    sphere.DrawSubset(0);

    Функция построения тора:

    D3DXCreateTorus(
    ссылка на устройство,
    внутренний радиус тора,
    внешний радиус тора,
    количество разбиений в поперечном сечении,
    количество разбиений по кругу,
    результат,
    указатель на смежные треугольники);
    Разбиений в сечение = 6

    Разбиений по кругу = 32

    Разбиений в сечение = 32

    Разбиений по кругу = 6

    C++
    LPD3DXMESH torus;
    D3DXCreateTorus(device, 0.2f, 0.8f, 32, 6, torus, NULL);
    torus->DrawSubset(0);
    Pascal
    var torus: ID3DXMesh;
    D3DXCreateTorus(device, 0.2, 0.8, 32, 6, torus, nil);
    
    torus.DrawSubset(0);

    Функция построения чайника:

    D3DXCreateTeapot(
    ссылка на устройство,
    результат,
    указатель на смежные треугольники);

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

    C++
    LPD3DXMESH teapot;
    D3DXCreateTeapot(device, teapot, NULL);
    teapot->DrawSubset(0);
    Pascal
    var teapot: ID3DXMesh;
    D3DXCreateTeapot(device, teapot, nil);
    
    teapot.DrawSubset(0);

    Ниже приведены примеры освещения трехмерного объекта точечным источником света с различными характеристиками.

    light.Ambient = D3DXCOLOR(0.3f, 0,0,0);
    light.Diffuse = D3DXCOLOR(0.5f, 0.0, 0,0);
    light.Ambient = D3DXCOLOR(0.2f, 0,0,0);
    light.Diffuse = D3DXCOLOR(0.5, 0.0, 0,0);
    light.Ambient = D3DXCOLOR(0.2, 0,0,0);
    light.Specular = D3DXCOLOR(0.3,0,0.0,0);

    Следует заметить, что зеркальное освещение требует больших вычислений, чем другие типы освещения, поэтому библиотека Direct3DM предоставляет возможность отключать его (по умолчанию оно выключено). Для включения зеркального освещения необходимо установить константу состояния D3DRS_SPECULARENABLE в значение "истина".

    C++ device->SetRenderState( D3DRS_SPECULARENABLE, TRUE );
    Pascal device.SetRenderState(D3DRS_SPECULARENABLE, 1);

    Как мы уже говорили, библиотека Direct3D позволяет одновременно использовать (обсчитывать) в сцене до восьми источников света. Программно использование нескольких источников света в сцене накладывает на программиста дополнительные "обязанности" по созданию и заполнению полей структуры для каждого источника. Ниже приведен пример использования трех разноцветных точечных источников света в сцене, которые расположены на координатных осях системы координат.

    C++
    D3DMATERIAL9 material;
    D3DLIGHT9 light0, light1, light2;
    
    ZeroMemory( material, sizeof(D3DMATERIAL9) );
    material.Diffuse=D3DXCOLOR(1.0f, 1.0f, 1.0f, 0.0f);
    
    ZeroMemory( light0, sizeof(D3DLIGHT9) );
    light0.Type = D3DLIGHT_POINT;
    light0.Diffuse = D3DXCOLOR(0.5f, 0.0f, 0.0f, 0.0f);
    light0.Position = D3DXVECTOR3(4.0f, 0.0f, 0.0f);
    …
    ZeroMemory( light1, sizeof(D3DLIGHT9) );
    light1.Type = D3DLIGHT_POINT;
    light1.Diffuse = D3DXCOLOR(0.0f, 0.5f, 0.0f, 0.0f);
    light1.Position = D3DXVECTOR3(0.0f, 0.0f, -4.0f);
    …
    ZeroMemory( light2, sizeof(D3DLIGHT9) );
    light2.Type = D3DLIGHT_POINT;
    light2.Diffuse = D3DXCOLOR(0.0f, 0.0f, 0.5f, 0.0f);
    light2.Position = D3DXVECTOR3(-4.0f, 0.0f, 0.0f);
    
    device->SetRenderState( D3DRS_LIGHTING, TRUE );
    ...
    // процедура рендеринга
    device->SetLight( 0, light0 );
    device->LightEnable( 0, TRUE );
    device->SetLight( 1, light1 );
    device->LightEnable( 1, TRUE );
    device->SetLight( 2, light2 );
    device->LightEnable( 2, TRUE );
    Pascal
    var
      material: TD3DMaterial9;
      light0, light1, light2: TD3DLight9;
    
    ZeroMemory(@material,SizeOf(TD3DMaterial9));
    material.Diffuse:=D3DXColor(1,1,1,0);
    
    light0._Type:=D3DLIGHT_POINT;
    light0.Diffuse:=D3DXColor(0.5, 0.0, 0, 0);
    light0.Position:=D3DXVector3(4,0,0);
    …
    light1._Type:=D3DLIGHT_POINT;
    light1.Diffuse:=D3DXColor(0.0, 0.5, 0, 0);
    light1.Position:=D3DXVector3(0,0,-4);
    …
    light2._Type:=D3DLIGHT_POINT;
    light2.Diffuse:=D3DXColor(0.0, 0.0, 0.5, 0);
    light2.Position:=D3DXVector3(-4,0,0);
    …
    device.SetRenderState(D3DRS_LIGHTING, 1);
    
    // процедура рендеринга
    device.SetLight(0,light0);
    device.LightEnable(0,true);
    device.SetLight(1,light1);
    device.LightEnable(1,true);
    device.SetLight(2,light2);
    device.LightEnable(2,true);

    Сложные (содержащие десятки тысяч вершин) объекты, как правило, создаются в профессиональных трехмерных редакторах (3D Studio, Maya, LightWave, и т.п.). Библиотека Direct3D содержит средства для загрузки и визуализации моделей, содержащихся в x-файлах. Х-файл – файл, содержащий в себе данные о трехмерном объекте (координаты вершин, материалы, нормали, текстуры и анимацию). Вначале необходимо объявить нужные переменные.

    C++ LPD3DXMESH mesh;
    Pascal var mesh: ID3DXMesh;

    Для загрузки трехмерного объекта (модели) из Х-файла предназначена функция

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

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

    C++
    D3DXLoadMeshFromX("ship.x", D3DXMESH_MANAGED, 
                      device, NULL, NULL, NULL, NULL, mesh);
    Pascal
    D3DXLoadMeshFromX('ship.x', D3DXMESH_MANAGED, 
                      device, nil, nil, nil, nil, mesh);

    Для визуализации трехмерной модели из Х-файла необходимо вызвать метод DrawSubset интерфейса ID3DXMesh, где в качестве параметра передается номер выводимой группы (подмножества) модели. Если модель не разбита на группы, то параметр метода равен нулю.

    C++ mesh->DrawSubset(0);
    Pascal mesh.DrawSubset(0);

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

    Реалистичные построения

    Принцип текстурирования трехмерных объектов ничем не отличается от двумерного аналога. Для каждой треугольной грани необходимо задать текстурные координаты. Единственное отличие от двумерного варианта заключается в формате вершин и наборе FVF флагов.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        FLOAT u, v;
    };
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_TEX1);
    ...
    VERTEX3D points[3] = {
            { -1.0f,-1.0f, 0.0f, 0.0f, 1.0f, },
            {  0.0f, 1.0f, 0.0f, 0.5f, 0.0f, },
            {  1.0f,-1.0f, 0.0f, 1.0f, 1.0f, }
    }
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          u, v: Single;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_TEX1;
    
    var 
      points: array [0..2] of Vertex3D = (
       (x: -1; y: -1; z: 0; u: 0;   v: 1),
       (x:  0; y:  1; z: 0; u: 0.5; v: 0),
       (x:  1; y: -1; z: 0; u: 1;   v: 1)
      );

    Ниже показан пример вывода куба, у которого все грани покрыты различными текстурами. Для этого можно объявить массив для хранения текстур из шести элементов типа IDirect3DTexture9.

    C++
    LPDIRECT3DTEXTURE9 textures[6];
    LPCSTR files[] = {
      {"bricks.bmp"}, {"colors.bmp"}, {"Lake.bmp"},
      {"stone_wall.bmp"}, {"wall.bmp"}, {"fur.jpg"} };
    ...
    for (int i=0; i<6; i++) 
      D3DXCreateTextureFromFile( device, files[i], textures[i] );
    Pascal
    var
      textures: array [0..5] of IDirect3DTexture9;
      files: array [0..5] of AnsiString = (
      ('bricks.bmp'), ('colors.bmp'), ('Lake.bmp'),
      ('stone_wall.bmp'), ('wall.bmp'), ('fur.jpg') );
    ...
    for i:=0 to 5 do
      D3DXCreateTextureFromFile(device, PChar(files[i]), textures[i]);

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

    C++
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    for i:=0 to 5 do begin
      device.SetTexture(0, textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

    Ниже приведены примеры (кадры анимации) вращения такого куба.

    Рассмотрим один из способов построения полупрозрачных трехмерных объектов. Данный метод прозрачности применим только для выпуклых объектов. Идея его заключается в следующем. Вначале выводятся те грани объекта, которые считаются невидимыми для наблюдателя (задние грани). Затем включается режим полупрозрачности (смешивания цветов). Выводим грани объекта, которые видны наблюдателю. Отображение "передних" и "задних" для наблюдателя сторон объекта можно осуществить с помощью механизма отсечения граней. Предположим, что вершины грани перечислены по часовой стрелке. Тогда отключение вывода таких граней отобразит нам "задние" стороны примитива (в нашем случае куба). Программно этот шаг может выглядеть так.

    C++
    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
    for i:=0 to 5 do begin
      device.SetTexture(0,textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

    Ниже показан пример вывода "внутренних" сторон куба.

    Включение полупрозрачности с нужными параметрами смешивания цветов.

    C++
    device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR );
    device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR );
    Pascal
    device.SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
    device.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
    device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);

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

    C++
    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
    for (int i=0; i<6; i++) {
      device->SetTexture(0, textures[i]);
      device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    }
    Pascal
    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
    for i:=0 to 5 do begin
      device.SetTexture(0,textures[i]);
      device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2);
    end;

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

    Для придания трехмерной сцене еще большей реалистичности можно добавить тени, которые отбрасывают объекты при освещении их некоторым источником света. Алгоритмы затенения в случае точечных источников света схожи с алгоритмами удаления невидимых поверхностей, с единственным лишь отличием. В алгоритмах удаления невидимых граней определяются поверхности, которые можно увидеть из точки положения наблюдателя, а в алгоритмах затенения выделяются поверхности, которые можно "увидеть" из местоположения источника света. Грани, видимые как из точки наблюдения, так и из источника света, не лежат в тени. Те же поверхности, которые видимы из точки наблюдения, но не видимы из источника света, находятся в тени.

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

  • Создать луч, начинающийся в источнике света, проходящий через вершину полигона и пересекающий плоскость проецирования;
  • Получить (найти) точку пересечения этого луча с плоскостью;
  • Проделать шаги 1 и 2 для всех вершин грани, получив таким образом координаты "теневого" полигона и затем отрисовать его обычными методами.
  • Пусть у нас источник света находится в точке L=(Lx,Ly,Lz), треугольная грань объекта имеет координаты вершин P=(Px,Py,Pz), Q=(Qx,Qy,Qz), R=(Rx,Ry,Rz), а плоскость проецирования (затенения) $$\alpha$$ задается неявным уравнением: Ax+By+Cz+D=0. Требуется определить координаты проекций вершин P,Q,R на плоскость $$\alpha.$$ Обозначим через M=(Mx,My,Mz),N=(Nx,Ny,Nz),O=(Ox,Oy,Oz) - проекции точек P,Q,R соответственно на плоскость $$\alpha.$$ Найдем координаты точки M. Так как точка M лежит на луче, то будет справедливы следующие уравнения$$\left\{ \begin{array}{ccc} Mx=Lx+t(Px-Lx) \\ My=Ly+t(Py-Ly) \\ Mz=Lz+t(Pz-Lz) \end{array} \right,$$ для некоторого параметра t.

    С другой стороны точка M лежит в плоскости $$\alpha,$$ поэтому верно уравнение: A*Mx+B*My+C*Mz+D=0.

    Подставим выражения для Mx,My,Mz в уравнение плоскости: A*(Lx+t(Px-Lx))+B*(Ly+t(Py-Ly))+C*(Lz+t(Pz-Lz))+D=0. Это линейное уравнение относительно параметра t, решая которое находим, что$$t^{.}=-\frac{A*Lx+B*Ly+C*Lz+D}{A(Px-Lx)+B(Py-Ly)+C(Pz-Lz)}.$$ Если $$t^{.} 0$$, то координаты точки пересечения луча и плоскости вычисляются по следующим формулам:$$\left\{ \begin{array}{ccc} Mx=Lx+t^{.}(Px-Lx) \\ My=Ly+t^{.}(Py-Ly) \\ Mz=Lz+t^{.}(Pz-Lz) \end{array}\right.$$

    Аналогичные шаги проделываем для двух других вершин полигона Q и R. Следует заметить, что при вычислении тени на плоскость y=0, параметр $$t^{.}=\frac{Ly}{Ly-Py}$$.

    Рассмотренный метод имеет несколько "подводных камней". Тень будет прорисовываться даже в том случае, если источник света находится между полигоном и плоскостью проецирования или же источник света расположен вообще по другую сторону от плоскости. Поэтому такие ситуации необходимо отслеживать и корректно обрабатывать.

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

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

    Разберем построение трехмерной поверхности, представляющей собой функцию двух переменных y=f(x,z). Пусть для простоты у нас область определения функции f(x,z) ограничена единичным квадратом, т.е. 0<=x<=1, 0<=z<=1. Визуализация рассмотренных поверхностей на растровых устройствах может быть осуществлена путем кусочно-линейной аппроксимации треугольными гранями. Такая триангуляционная поверхность может быть получена следующим образом. Параметрическое пространство x,z (единичный квадрат) дискретизируется по координате x с шагом dx, по координате z – с шагом dz. Для каждой пары значений (x,z) вычисляется точка на поверхности y=f(x,z). Полученный таким образом массив точек триангулируется регулярной сеткой.

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

    Разберем один из способов построения ландшафтов, который базируется на так называемой карте высот. Карта высот представляет собой двумерный массив, где каждый элемент (пиксель) определяет высоту точки в сетке треугольников. Как правило, значение высоты ранжируется в диапазоне от 0 до 255. Поэтому для хранения подобных карт высот используют изображения в оттенках серого цвета ( grayscale image ). Более светлые участки на таких изображениях соответствуют более высоким значениям точек, а темные участки определяют низкие значения высот. Кроме этого подбирается текстура, схожая по "характеру" с ландшафтом. Ниже приведен пример построения ландшафта по карте высот.

    Вернуться к учебному плану