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

Трехмерная графика с использованием Direct3D

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

Схема графического конвейера

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

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

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

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

Отсечение по видимому объему задает пирамиду видимости, и примитивы, которые находятся вне этой пирамиды, будут отсекаться.

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

Теперь нормализованные координаты отображаются в пространство экрана. На этом шаге "жизнь" вершин в графическом конвейере заканчивается.

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

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

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

Принципы построения трехмерной сцены

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

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

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

  • объекты;
  • наблюдатель (камера);
  • источники света;
  • свойства материалов объекта.
  • При этом моделируемая трехмерная сцена может описываться тысячами, а иногда миллионами вершин. Для каждой вершины (точки) примитива должны выполняться вычисления по одним и тем же формулам.

    Как мы знаем, точка в 3D графике задается, как правило, набором из 4-х значений (x,y,z,w). Реальные координаты точки в пространстве будут (x/w, y/w, z/w), компонент w является масштабом. Обычно для точек w=1. Вектор – направленный отрезок. Вектора равны если у них одинаковая длина и направление. Вектора также задаются в виде линейного массива 1х4 (x,y,z,w), но компонент w у векторов равен 0. Для выполнения преобразований с вершинами используют матричный подход. Матрица размерности MxN – прямоугольная таблица, имеющая M строк, N столбцов и заполненная элементами одного типа. В 3D графике используют, как правило, матрицы размерности 4х4. Таким образом преобразование точки в пространстве сводится к умножению вектор-строки размерности 4 на матрицу преобразования размером 4х4:

    $$(x y z w)*\left( \begin{array}{ccc} A_{11} A_{12} A_{13} A_{14} \\ A_{21} A_{22} A_{23} A_{24} \\ A_{11} A_{32} A_{33} A_{34} \\ A_{41} A_{42} A_{43} A_{44} \end{array} \right) = (x' y' z' w')$$

    Так, например, умножение всех вершин объекта на одну из матриц вращения приведет к вращению этого объекта вокруг оси Ox, Oy или Oz соответственно. Для сложной трансформации объекта можно использовать последовательные преобразования, которые выражаются в перемножении (конкатенации) соответствующих матриц элементарных преобразований. Таким образом, можно сначала рассчитать единую (общую) матрицу преобразования (перемножить между собой все элементарные матрицы трансформации), а затем использовать только ее. Так, например, вращение объекта вокруг своей оси и одновременное движение по кругу определенного радиуса, может быть описано следующей последовательностью матриц: Vi *MatRot1(…)*MatTrans(…)*MatRot2(…), где Vi – координаты вершин объекта, MatRot1 – матрица поворота вокруг своей оси, MatTrans – матрица перемещения, MatRot2 – матрица вращения по кругу.

    Вам не придется самостоятельно запоминать и вычислять все матрицы преобразования "вручную", т.к. во всех библиотеках 3D графики они предусмотрены. В библиотеке Direct3D определен матричный тип D3DXMATRIX – это структура, которая содержит 4х4 элементов.

    Элемент матрицы с индексами ij указывает на значение, хранящееся в строке с номером i и столбце с номером j. Например, элемент _32 указывает на значение m[3][2]. Обращаться к элементам матрицы (читать, записывать) можно двумя способами.

    C++
    D3DXMATRIX m; 
    m._11 = …;
    или
    m.m[1][1] = …;
    Pascal
    var  m: TD3DXMatrix;
    …
    m._11 := …;
    или
    m.m[1,1] = …;

    Библиотека Direct3D содержит богатый набор процедур и функций по работе с матрицами. Приведем основные функции для работы с матрицами.

    C++
    D3DXMATRIX m;
    
    // Создание единичной матрицы
    D3DXMatrixIdentity(m);
    
    // Создание матрицы перемещения
    D3DXMatrixTranslation(m, dx, dy, dz);
    
    // Создание матрицы масштабирования
    D3DXMatrixScaling(m, kx, ky, kz);
    
    // Создание матриц вращения
    D3DXMatrixRotationX(m, angleX);
    D3DXMatrixRotationY(m, angleY);
    D3DXMatrixRotationZ(m, angleZ);
    Pascal
    var
      m: TD3DXMatrix;
    
    // Создание единичной матрицы
    D3DXMatrixIdentity(m);
    
    // Создание матрицы перемещения
    D3DXMatrixTranslation(m, dx, dy, dz);
    
    // Создание матрицы масштабирования
    D3DXMatrixScaling(m, kx, ky, kz);
    
    // Создание матриц вращения
    D3DXMatrixRotationX(m, angleX);
    D3DXMatrixRotationY(m, angleY);
    D3DXMatrixRotationZ(m, angleZ);

    При работе с матрицами следует учитывать, что преобразования масштабирования и вращения производятся относительно начала координат. Для комбинирования (умножения) двух матриц существует функция D3DXMatrixMultiply(), которая помещает результат перемножения двух матриц, которые передаются в качестве второго и третьего аргументов, в первый. К примеру, мы хотим повернуть объект вокруг оси Oy на угол 30 градусов и переместить его на вектор (1,2,3). Для этого можно воспользоваться правилом композиции двух элементарных преобразований с помощью матрицы поворота и матрицы перемещения.

    C++
    D3DXMATRIX matRotY, matTrans, matRes;
    D3DXMatrixRotationY( matRotY, 30*D3DX_PI/180 );
    D3DXMatrixTranslation( matTrans, 1, 2, 3 );
    D3DXMatrixMultiply(matRes, matRotY, matTrans);
    Pascal
    var 
      matRotY, matTrans, matRes: TD3XDMatrix;
    
    D3DXMatrixRotationY( matRotY, 30*pi/180 );
    D3DXMatrixTranslation( matTrans, 1, 2, 3 );
    D3DXMatrixMultiply( matRes, matRotY, matTrans );

    Если поменять местами матрицы matRotY и matTrans в функции D3DXMatrixMultiply, то результат преобразования будет иной (сначала объект будет перемещен, а затем повернут). Вообще функция D3DXMatrixMultiply сама возвращает результат перемножения двух матриц, и поэтому ее можно использовать как параметр в матричных операциях. Ниже приведен пример такой возможности.

    C++
    D3DXMATRIX matRotY, matTrans;
    D3DXMatrixRotationY( matRotY, … );
    D3DXMatrixTranslation( matTrans, … );
    device->SetTransform(D3DTS_VIEW, 
                         D3DXMatrixMultiply(NULL, matRotY, matTrans));
    Pascal
    var 
      matRotY, matTrans, matRes: TD3XDMatrix;
    
    D3DXMatrixRotationY( matRotY, … );
    D3DXMatrixTranslation( matTrans, … );
    device.SetTransform(D3DTS_VIEW, 
                        D3DXMatrixMultiply(nil, matRotY, matTrans)^);

    Для визуализации некоторого объекта (получение его проекции) нам необходимы следующие данные:

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

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

    Библиотека Direct3D позволяет работать как в левосторонней системе координат, так и в правосторонней. Далее будем рассматривать все примеры для левосторонней системы координат.

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

    После того как заданы все объекты в своих собственных (локальных) системах координат, необходимо привязать их к общей мировой системе координат. Процесс трансформации координат объектов, заданных в локальных системах в мировую (общую) называют мировым преобразованием ( world transform ). Обычно используют 3 типа преобразований: перемещение, масштабирование, вращение. Мировое преобразование описывается с помощью матрицы, используя метод SetTransform интерфейса IDirect3DDevice9. Для применения мирового преобразования метод SetTransform вызывается с параметром D3DTS_WORLD. Предположим, что у нас в сцене присутствуют два объекта: куб и сфера. Мы собираемся отобразить куб в точке с координатами (-3, 2, 6), а сферу в точке (5, 0, -2) мировой системы координат. Это можно проделать с помощью следующих шагов.

    C++
    D3DXMATRIX matCube, matSphere;
    
    D3DXMatrixTranslation(matCube, -3.0f, 2.0f, 6.0f);
    pDirect3DDevice->SetTransform(D3DTS_WORLD, matCube);
    drawCube();
      
    D3DXMatrixTranslation(matSphere, 5.0f, 0.0f, -2.0f);
    pDirect3DDevice->SetTransform(D3DTS_WORLD, matSphere);
    drawSphere();
    Pascal
    var 
      matCube, matSphere: TD3DXMatrix;
    …
    D3DXMatrixTranslation(matCube, -3.0, 2.0, 6.0);
    device.SetTransform(D3DTS_WORLD, matCube);
    drawCube;
    
    D3DXMatrixTranslation(matShpere, 5.0, 0.0, -2.0);
    device.SetTransform(D3DTS_WORLD, matSphere);
    drawSphere;

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

    Матрица перехода в систему координат камеры может быть получена с помощью функции D3DXMatrixLookAtLH(), прототип которой показан ниже:

    D3DXMatrixLookAtLH
    (
    matView, // результат 
    Eye, // положение камеры в мировой системе координат
    At, // точка в мировой системе, куда направлена камера (взгляд)
    Up // вектор, указывающий "где верх" в мировой системе координат
    ).

    Для установки матрицы вида (преобразование в пространство камеры) используется метод SetTransform с первым параметром D3DTS_VIEW.

    Например, поместить камеру в точку (0,0,-3) и направить "взгляд" наблюдателя в начало системы координат, можно с помощью такого кода:

    C++
    D3DXMATRIX matView;
    D3DXVECTOR3 positionCamera, targetPoint, worldUp;
    
    positionCamera = D3DXVECTOR3(0,0,-3);
    targetPoint = D3DXVECTOR3(0,0,0);
    worldUp = D3DXVECTOR3(0,1,0);
    	
    D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, 
      worldUp);
    device->SetTransform(D3DTS_VIEW, matView);
    Pascal
    var matView: TD3DMatrix;
        positionCamera, targetPoint, worldUp : TD3DXVector3;
    …
    positionCamera := D3DXVector3(0, 0, -3);
    targetPoint := D3DXVector3(0, 0, 0);
    worldUp := D3DXVector3(0, 1, 0);
    
    D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, 
      worldUp);
    device.SetTransform(D3DTS_VIEW, matView);

    Таким образом, все объекты сцены будут теперь описаны в системе координат камеры (наблюдателя). Если положение и параметры камеры не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_VIEW производится один раз.

    Последним этапом преобразования при получении 2D изображения является операция проекции. Библиотека поддерживает работу как с перспективной, так и с ортогональной проекциями. Для работы с первым типом проекций предназначена функция D3DXMatrixPerspectiveFovLH(), для ортогональных же проекций нужна функция D3DXMatrixOrthoOffCenterLH(). Мы рассмотрим примеры работы с перспективной проекцией. Матрица перспективной проекции задает положение передней и задней отсекающих плоскостей, искажения, имитирующие перспективу. Прототип функции D3DXMatrixPerspectiveFovLH() приведен ниже.

    D3DXMatrixPerspectiveFovLH(
    matProj, // результат 
    fov, // вертикальный угол обзора
    aspect, // отношение ширины окна к высоте
    zn, // расстояние до передней отсекающей плоскости
    zf // расстояние до задней отсекающей плоскости
    )

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

    Для установки перспективной матрицы необходимо вызвать метод SetTransform с первым параметром D3DTS_PROJECTION. Приведенный ниже пример, создает и устанавливает матрицу проекции со следующими параметрами: вертикальный угол обзора – 45 градусов, расстояние до передней и задней отсекающих плоскостей – 1 и 100 единиц соответственно.

    C++
    D3DXMATRIX matProj;
    
    D3DXMatrixPerspectiveFovLH(matProj, D3DX_PI/2, 
      width/height, 1, 100);
    device->SetTransform(D3DTS_PROJECTION, matProj);
    Pascal
    var 
      matProj: TD3DXMatrix;
    
    D3DXMatrixPerspectiveFovLH(matProj, pi/2, 
      Width/Height, 1, 100);
    device.SetTransform(D3DTS_PROJECTION, matProj);

    Если параметры проецирования не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_PROJECTION выполняется единожды.

    Таким образом, конвейер преобразований вершин объекта может быть описан следующей блок-схемой:

    Каждая вершина трехмерной сцены подвергается следующему преобразованию V' = V*matWorld*matView*matProj, где V – исходная вершина, V' - преобразованная вершина.

    Буфер глубины

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

  • Вычисление глубины (z-координаты) в точке (x,y) ;
  • Если z(x,y) меньше чем значение в z-буфере в позиции (x,y) то в z-буфер заносится значение z-координаты растеризуемой точки, а в буфер регенерации помещается обрабатываемый пиксель.
  • Если выполняется условие шага 2, это означает, что точка многоугольника расположена ближе к наблюдателю, чем точка, значение яркости которой находится в данный момент в позиции (x,y) буфера регенерации.

    Единственный недостаток рассмотренного алгоритма – необходимость дополнительной памяти для хранения z-буфера, однако простая и эффективная реализация позволяет использовать этот алгоритм во многих приложениях. Для инициализации буфера глубины в библиотеке Direct3D достаточно заполнить два поля структуры D3DPRESENT_PARAMETERS:

    C++
    D3DPRESENT_PARAMETERS params;
    …
    ZeroMemory( params, sizeof(params) );
    …
    params.EnableAutoDepthStencil = true;
    params.AutoDepthStencilFormat = D3DFMT_D16;
    …
    Pascal
    var
    params: TD3DPresentParameters;
    …
    ZeroMemory( @params, SizeOf(params) );
    …
    params.EnableAutoDepthStencil := true;
    params.AutoDepthStencilFormat := D3DFMT_D16;
    …

    Программист может не беспокоиться о линейных размерах буфера глубины, т.к. система автоматически определяет размеры. Для работы с буфером глубины необходимо также установить константу режима D3DRS_ZENABLE в значение "истина".

    C++ device->SetRenderState (D3DRS_ZENABLE, D3DZB_TRUE);
    Pascal device.SetRenderState(D3DRS_ZENABLE, 1);

    Чтобы отключить z-буфер, нужно установить данную константу D3DRS_ZENABLE в значение "ложь".

    C++ device->SetRenderState (D3DRS_ZENABLE, D3DZB_FALSE);
    Pascal device.SetRenderState(D3DRS_ZENABLE, 0);

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

    C++
    device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,             
                             D3DCOLOR_XRGB(255,255,255), 1.0f, 0 );
    Pascal
    device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,
                           D3DCOLOR_XRGB(255,255,255), 1.0, 0 );

    Предпоследний параметр метода Clear задает значение, которым будем заполнен буфер глубины при очистке. Значение единица соответствует бесконечно удаленной точке трехмерного пространства; значение ноль – определяет самую близкую точку.

    Вывод трехмерных объектов

    В первую очередь при визуализации трехмерных объектов, необходимо изменить формат вершины и набор FVF флагов.

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

    Единственное отличие от двумерного случая будет заключаться в наличии операций с матрицами для преобразования объектов. Функции для визуализации трехмерных объектов будут такими же, какие мы использовали в двумерном случае: DrawPrimitive, DrawPrimitiveUP, DrawIndexedPrimitive, DrawIndexedPrimitiveUP. В качестве примера разберем построение, вращающего вокруг одной из координатных осей, цветного треугольника. Запишем шаги, которые мы должны проделать.

  • Определение формата вершин треугольника и набор FVF флагов;
  • Заполнение массива вершин данными;
  • Установка видовой и проекционной матриц;
  • Установка мировой матрицы (поворот вокруг оси) и вывод примитива.
  • Приведем программные строки для каждого шага алгоритма.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        DWORD color;
    };
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_DIFFUSE);
    ...
    VERTEX3D points[3] = {
            { -1.0f,-1.0f, 0.0f, 0x00ff0000, },
            {  0.0f, 1.0f, 0.0f, 0x0000ff00, },
            {  1.0f,-1.0f, 0.0f, 0x000000ff, }
    }
    ...
    D3DXMATRIX matWorld, matView, matProj;
    
    D3DXMatrixLookAtLH( matView,  D3DXVECTOR3 ( 0.0f, 0.0f,-5.0f ),
                                   D3DXVECTOR3 ( 0.0f, 0.0f, 0.0f ),
                                   D3DXVECTOR3 ( 0.0f, 1.0f, 0.0f ) );
    device->SetTransform( D3DTS_VIEW, matView );
    
    D3DXMatrixPerspectiveFovLH( matProj, D3DX_PI/4, 1.0f, 1.0f, 
      100.0f );
    device->SetTransform( D3DTS_PROJECTION, matProj );
    ...
    D3DXMatrixRotationY( matWorld, angle );
    device->SetTransform( D3DTS_WORLD, matWorld );
    
    device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          color: DWORD;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_DIFFUSE;
    
    var 
      points: array [0..2] of Vertex3D = (
       (x: -1; y: -1; z: 0; color: $000000ff),
       (x:  0; y:  1; z: 0; color: $0000ff00),
       (x:  1; y: -1; z: 0; color: $00ff0000)
      );
    
      matWorld, matView, matProj: TD3DMatrix;
    ...
    
    
    D3DXMatrixLookAtLH(matView,
                       D3DXVector3(0,0,-5),
                       D3DXVector3(0,0,0),
                       D3DXVector3(0,1,0));
    device.SetTransform(D3DTS_VIEW, matView);
    
    D3DXMatrixPerspectiveFovLH(matProj, PI/4, 1, 1, 100);
    device.SetTransform(D3DTS_PROJECTION, matProj);
    ...
    D3DXMatrixRotationY(matWorld, angle);
    device.SetTransform(D3DTS_WORLD, matWorld);
    device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    Ниже приведен пример нескольких кадров из анимации.

    Библиотека Direct3D располагает механизмом отсечения граней. Идея заключается в том, что можно указать порядок обхода вершин треугольника (по часовой или против часовой стрелки) и отключить вывод тех граней, которые перечислены, например, по часовой стрелке. По умолчанию механизм отсечения граней отключен, т.е. воспроизводятся обе стороны примитива. Программно отключение "задних" сторон примитива реализуется через вызов метода SetRenderState(D3DRS_CULLMODE, <значение>) интерфейса IDirect3DDevice9. В качестве второго параметра может выступать одна из трех констант: D3DCULL_NONE – механизм отбраковки граней выключен;

    D3DCULL_CW – отбраковываются грани, вершины которых перечислены по часовой стрелке;

    D3DCULL_CCW – отбраковываются грани, вершины которых перечислены против часовой стрелки.

    Так, на представленном ниже рисунке грань (треугольник) задается перечислением своих вершин: v0, v1, v2 – по часовой стрелке; v0, v2, v1 – против часовой стрелки.

    Страницы:

    Схема графического конвейера

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

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

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

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

    Отсечение по видимому объему задает пирамиду видимости, и примитивы, которые находятся вне этой пирамиды, будут отсекаться.

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

    Теперь нормализованные координаты отображаются в пространство экрана. На этом шаге "жизнь" вершин в графическом конвейере заканчивается.

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

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

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

    Принципы построения трехмерной сцены

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

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

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

  • объекты;
  • наблюдатель (камера);
  • источники света;
  • свойства материалов объекта.
  • При этом моделируемая трехмерная сцена может описываться тысячами, а иногда миллионами вершин. Для каждой вершины (точки) примитива должны выполняться вычисления по одним и тем же формулам.

    Как мы знаем, точка в 3D графике задается, как правило, набором из 4-х значений (x,y,z,w). Реальные координаты точки в пространстве будут (x/w, y/w, z/w), компонент w является масштабом. Обычно для точек w=1. Вектор – направленный отрезок. Вектора равны если у них одинаковая длина и направление. Вектора также задаются в виде линейного массива 1х4 (x,y,z,w), но компонент w у векторов равен 0. Для выполнения преобразований с вершинами используют матричный подход. Матрица размерности MxN – прямоугольная таблица, имеющая M строк, N столбцов и заполненная элементами одного типа. В 3D графике используют, как правило, матрицы размерности 4х4. Таким образом преобразование точки в пространстве сводится к умножению вектор-строки размерности 4 на матрицу преобразования размером 4х4:

    $$(x y z w)*\left( \begin{array}{ccc} A_{11} A_{12} A_{13} A_{14} \\ A_{21} A_{22} A_{23} A_{24} \\ A_{11} A_{32} A_{33} A_{34} \\ A_{41} A_{42} A_{43} A_{44} \end{array} \right) = (x' y' z' w')$$

    Так, например, умножение всех вершин объекта на одну из матриц вращения приведет к вращению этого объекта вокруг оси Ox, Oy или Oz соответственно. Для сложной трансформации объекта можно использовать последовательные преобразования, которые выражаются в перемножении (конкатенации) соответствующих матриц элементарных преобразований. Таким образом, можно сначала рассчитать единую (общую) матрицу преобразования (перемножить между собой все элементарные матрицы трансформации), а затем использовать только ее. Так, например, вращение объекта вокруг своей оси и одновременное движение по кругу определенного радиуса, может быть описано следующей последовательностью матриц: Vi *MatRot1(…)*MatTrans(…)*MatRot2(…), где Vi – координаты вершин объекта, MatRot1 – матрица поворота вокруг своей оси, MatTrans – матрица перемещения, MatRot2 – матрица вращения по кругу.

    Вам не придется самостоятельно запоминать и вычислять все матрицы преобразования "вручную", т.к. во всех библиотеках 3D графики они предусмотрены. В библиотеке Direct3D определен матричный тип D3DXMATRIX – это структура, которая содержит 4х4 элементов.

    Элемент матрицы с индексами ij указывает на значение, хранящееся в строке с номером i и столбце с номером j. Например, элемент _32 указывает на значение m[3][2]. Обращаться к элементам матрицы (читать, записывать) можно двумя способами.

    C++
    D3DXMATRIX m; 
    m._11 = …;
    или
    m.m[1][1] = …;
    Pascal
    var  m: TD3DXMatrix;
    …
    m._11 := …;
    или
    m.m[1,1] = …;

    Библиотека Direct3D содержит богатый набор процедур и функций по работе с матрицами. Приведем основные функции для работы с матрицами.

    C++
    D3DXMATRIX m;
    
    // Создание единичной матрицы
    D3DXMatrixIdentity(m);
    
    // Создание матрицы перемещения
    D3DXMatrixTranslation(m, dx, dy, dz);
    
    // Создание матрицы масштабирования
    D3DXMatrixScaling(m, kx, ky, kz);
    
    // Создание матриц вращения
    D3DXMatrixRotationX(m, angleX);
    D3DXMatrixRotationY(m, angleY);
    D3DXMatrixRotationZ(m, angleZ);
    Pascal
    var
      m: TD3DXMatrix;
    
    // Создание единичной матрицы
    D3DXMatrixIdentity(m);
    
    // Создание матрицы перемещения
    D3DXMatrixTranslation(m, dx, dy, dz);
    
    // Создание матрицы масштабирования
    D3DXMatrixScaling(m, kx, ky, kz);
    
    // Создание матриц вращения
    D3DXMatrixRotationX(m, angleX);
    D3DXMatrixRotationY(m, angleY);
    D3DXMatrixRotationZ(m, angleZ);

    При работе с матрицами следует учитывать, что преобразования масштабирования и вращения производятся относительно начала координат. Для комбинирования (умножения) двух матриц существует функция D3DXMatrixMultiply(), которая помещает результат перемножения двух матриц, которые передаются в качестве второго и третьего аргументов, в первый. К примеру, мы хотим повернуть объект вокруг оси Oy на угол 30 градусов и переместить его на вектор (1,2,3). Для этого можно воспользоваться правилом композиции двух элементарных преобразований с помощью матрицы поворота и матрицы перемещения.

    C++
    D3DXMATRIX matRotY, matTrans, matRes;
    D3DXMatrixRotationY( matRotY, 30*D3DX_PI/180 );
    D3DXMatrixTranslation( matTrans, 1, 2, 3 );
    D3DXMatrixMultiply(matRes, matRotY, matTrans);
    Pascal
    var 
      matRotY, matTrans, matRes: TD3XDMatrix;
    
    D3DXMatrixRotationY( matRotY, 30*pi/180 );
    D3DXMatrixTranslation( matTrans, 1, 2, 3 );
    D3DXMatrixMultiply( matRes, matRotY, matTrans );

    Если поменять местами матрицы matRotY и matTrans в функции D3DXMatrixMultiply, то результат преобразования будет иной (сначала объект будет перемещен, а затем повернут). Вообще функция D3DXMatrixMultiply сама возвращает результат перемножения двух матриц, и поэтому ее можно использовать как параметр в матричных операциях. Ниже приведен пример такой возможности.

    C++
    D3DXMATRIX matRotY, matTrans;
    D3DXMatrixRotationY( matRotY, … );
    D3DXMatrixTranslation( matTrans, … );
    device->SetTransform(D3DTS_VIEW, 
                         D3DXMatrixMultiply(NULL, matRotY, matTrans));
    Pascal
    var 
      matRotY, matTrans, matRes: TD3XDMatrix;
    
    D3DXMatrixRotationY( matRotY, … );
    D3DXMatrixTranslation( matTrans, … );
    device.SetTransform(D3DTS_VIEW, 
                        D3DXMatrixMultiply(nil, matRotY, matTrans)^);

    Для визуализации некоторого объекта (получение его проекции) нам необходимы следующие данные:

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

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

    Библиотека Direct3D позволяет работать как в левосторонней системе координат, так и в правосторонней. Далее будем рассматривать все примеры для левосторонней системы координат.

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

    После того как заданы все объекты в своих собственных (локальных) системах координат, необходимо привязать их к общей мировой системе координат. Процесс трансформации координат объектов, заданных в локальных системах в мировую (общую) называют мировым преобразованием ( world transform ). Обычно используют 3 типа преобразований: перемещение, масштабирование, вращение. Мировое преобразование описывается с помощью матрицы, используя метод SetTransform интерфейса IDirect3DDevice9. Для применения мирового преобразования метод SetTransform вызывается с параметром D3DTS_WORLD. Предположим, что у нас в сцене присутствуют два объекта: куб и сфера. Мы собираемся отобразить куб в точке с координатами (-3, 2, 6), а сферу в точке (5, 0, -2) мировой системы координат. Это можно проделать с помощью следующих шагов.

    C++
    D3DXMATRIX matCube, matSphere;
    
    D3DXMatrixTranslation(matCube, -3.0f, 2.0f, 6.0f);
    pDirect3DDevice->SetTransform(D3DTS_WORLD, matCube);
    drawCube();
      
    D3DXMatrixTranslation(matSphere, 5.0f, 0.0f, -2.0f);
    pDirect3DDevice->SetTransform(D3DTS_WORLD, matSphere);
    drawSphere();
    Pascal
    var 
      matCube, matSphere: TD3DXMatrix;
    …
    D3DXMatrixTranslation(matCube, -3.0, 2.0, 6.0);
    device.SetTransform(D3DTS_WORLD, matCube);
    drawCube;
    
    D3DXMatrixTranslation(matShpere, 5.0, 0.0, -2.0);
    device.SetTransform(D3DTS_WORLD, matSphere);
    drawSphere;

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

    Матрица перехода в систему координат камеры может быть получена с помощью функции D3DXMatrixLookAtLH(), прототип которой показан ниже:

    D3DXMatrixLookAtLH
    (
    matView, // результат 
    Eye, // положение камеры в мировой системе координат
    At, // точка в мировой системе, куда направлена камера (взгляд)
    Up // вектор, указывающий "где верх" в мировой системе координат
    ).

    Для установки матрицы вида (преобразование в пространство камеры) используется метод SetTransform с первым параметром D3DTS_VIEW.

    Например, поместить камеру в точку (0,0,-3) и направить "взгляд" наблюдателя в начало системы координат, можно с помощью такого кода:

    C++
    D3DXMATRIX matView;
    D3DXVECTOR3 positionCamera, targetPoint, worldUp;
    
    positionCamera = D3DXVECTOR3(0,0,-3);
    targetPoint = D3DXVECTOR3(0,0,0);
    worldUp = D3DXVECTOR3(0,1,0);
    	
    D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, 
      worldUp);
    device->SetTransform(D3DTS_VIEW, matView);
    Pascal
    var matView: TD3DMatrix;
        positionCamera, targetPoint, worldUp : TD3DXVector3;
    …
    positionCamera := D3DXVector3(0, 0, -3);
    targetPoint := D3DXVector3(0, 0, 0);
    worldUp := D3DXVector3(0, 1, 0);
    
    D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, 
      worldUp);
    device.SetTransform(D3DTS_VIEW, matView);

    Таким образом, все объекты сцены будут теперь описаны в системе координат камеры (наблюдателя). Если положение и параметры камеры не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_VIEW производится один раз.

    Последним этапом преобразования при получении 2D изображения является операция проекции. Библиотека поддерживает работу как с перспективной, так и с ортогональной проекциями. Для работы с первым типом проекций предназначена функция D3DXMatrixPerspectiveFovLH(), для ортогональных же проекций нужна функция D3DXMatrixOrthoOffCenterLH(). Мы рассмотрим примеры работы с перспективной проекцией. Матрица перспективной проекции задает положение передней и задней отсекающих плоскостей, искажения, имитирующие перспективу. Прототип функции D3DXMatrixPerspectiveFovLH() приведен ниже.

    D3DXMatrixPerspectiveFovLH(
    matProj, // результат 
    fov, // вертикальный угол обзора
    aspect, // отношение ширины окна к высоте
    zn, // расстояние до передней отсекающей плоскости
    zf // расстояние до задней отсекающей плоскости
    )

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

    Для установки перспективной матрицы необходимо вызвать метод SetTransform с первым параметром D3DTS_PROJECTION. Приведенный ниже пример, создает и устанавливает матрицу проекции со следующими параметрами: вертикальный угол обзора – 45 градусов, расстояние до передней и задней отсекающих плоскостей – 1 и 100 единиц соответственно.

    C++
    D3DXMATRIX matProj;
    
    D3DXMatrixPerspectiveFovLH(matProj, D3DX_PI/2, 
      width/height, 1, 100);
    device->SetTransform(D3DTS_PROJECTION, matProj);
    Pascal
    var 
      matProj: TD3DXMatrix;
    
    D3DXMatrixPerspectiveFovLH(matProj, pi/2, 
      Width/Height, 1, 100);
    device.SetTransform(D3DTS_PROJECTION, matProj);

    Если параметры проецирования не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_PROJECTION выполняется единожды.

    Таким образом, конвейер преобразований вершин объекта может быть описан следующей блок-схемой:

    Каждая вершина трехмерной сцены подвергается следующему преобразованию V' = V*matWorld*matView*matProj, где V – исходная вершина, V' - преобразованная вершина.

    Буфер глубины

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

  • Вычисление глубины (z-координаты) в точке (x,y) ;
  • Если z(x,y) меньше чем значение в z-буфере в позиции (x,y) то в z-буфер заносится значение z-координаты растеризуемой точки, а в буфер регенерации помещается обрабатываемый пиксель.
  • Если выполняется условие шага 2, это означает, что точка многоугольника расположена ближе к наблюдателю, чем точка, значение яркости которой находится в данный момент в позиции (x,y) буфера регенерации.

    Единственный недостаток рассмотренного алгоритма – необходимость дополнительной памяти для хранения z-буфера, однако простая и эффективная реализация позволяет использовать этот алгоритм во многих приложениях. Для инициализации буфера глубины в библиотеке Direct3D достаточно заполнить два поля структуры D3DPRESENT_PARAMETERS:

    C++
    D3DPRESENT_PARAMETERS params;
    …
    ZeroMemory( params, sizeof(params) );
    …
    params.EnableAutoDepthStencil = true;
    params.AutoDepthStencilFormat = D3DFMT_D16;
    …
    Pascal
    var
    params: TD3DPresentParameters;
    …
    ZeroMemory( @params, SizeOf(params) );
    …
    params.EnableAutoDepthStencil := true;
    params.AutoDepthStencilFormat := D3DFMT_D16;
    …

    Программист может не беспокоиться о линейных размерах буфера глубины, т.к. система автоматически определяет размеры. Для работы с буфером глубины необходимо также установить константу режима D3DRS_ZENABLE в значение "истина".

    C++ device->SetRenderState (D3DRS_ZENABLE, D3DZB_TRUE);
    Pascal device.SetRenderState(D3DRS_ZENABLE, 1);

    Чтобы отключить z-буфер, нужно установить данную константу D3DRS_ZENABLE в значение "ложь".

    C++ device->SetRenderState (D3DRS_ZENABLE, D3DZB_FALSE);
    Pascal device.SetRenderState(D3DRS_ZENABLE, 0);

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

    C++
    device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,             
                             D3DCOLOR_XRGB(255,255,255), 1.0f, 0 );
    Pascal
    device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,
                           D3DCOLOR_XRGB(255,255,255), 1.0, 0 );

    Предпоследний параметр метода Clear задает значение, которым будем заполнен буфер глубины при очистке. Значение единица соответствует бесконечно удаленной точке трехмерного пространства; значение ноль – определяет самую близкую точку.

    Вывод трехмерных объектов

    В первую очередь при визуализации трехмерных объектов, необходимо изменить формат вершины и набор FVF флагов.

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

    Единственное отличие от двумерного случая будет заключаться в наличии операций с матрицами для преобразования объектов. Функции для визуализации трехмерных объектов будут такими же, какие мы использовали в двумерном случае: DrawPrimitive, DrawPrimitiveUP, DrawIndexedPrimitive, DrawIndexedPrimitiveUP. В качестве примера разберем построение, вращающего вокруг одной из координатных осей, цветного треугольника. Запишем шаги, которые мы должны проделать.

  • Определение формата вершин треугольника и набор FVF флагов;
  • Заполнение массива вершин данными;
  • Установка видовой и проекционной матриц;
  • Установка мировой матрицы (поворот вокруг оси) и вывод примитива.
  • Приведем программные строки для каждого шага алгоритма.

    C++
    struct VERTEX3D
    {
        FLOAT x, y, z;
        DWORD color;
    };
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_DIFFUSE);
    ...
    VERTEX3D points[3] = {
            { -1.0f,-1.0f, 0.0f, 0x00ff0000, },
            {  0.0f, 1.0f, 0.0f, 0x0000ff00, },
            {  1.0f,-1.0f, 0.0f, 0x000000ff, }
    }
    ...
    D3DXMATRIX matWorld, matView, matProj;
    
    D3DXMatrixLookAtLH( matView,  D3DXVECTOR3 ( 0.0f, 0.0f,-5.0f ),
                                   D3DXVECTOR3 ( 0.0f, 0.0f, 0.0f ),
                                   D3DXVECTOR3 ( 0.0f, 1.0f, 0.0f ) );
    device->SetTransform( D3DTS_VIEW, matView );
    
    D3DXMatrixPerspectiveFovLH( matProj, D3DX_PI/4, 1.0f, 1.0f, 
      100.0f );
    device->SetTransform( D3DTS_PROJECTION, matProj );
    ...
    D3DXMatrixRotationY( matWorld, angle );
    device->SetTransform( D3DTS_WORLD, matWorld );
    
    device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );
    Pascal
    type
      Vertex3D = packed record
          x, y, z: Single;
          color: DWORD;
      end;
    
    const
      MY_FVF = D3DFVF_XYZ or D3DFVF_DIFFUSE;
    
    var 
      points: array [0..2] of Vertex3D = (
       (x: -1; y: -1; z: 0; color: $000000ff),
       (x:  0; y:  1; z: 0; color: $0000ff00),
       (x:  1; y: -1; z: 0; color: $00ff0000)
      );
    
      matWorld, matView, matProj: TD3DMatrix;
    ...
    
    
    D3DXMatrixLookAtLH(matView,
                       D3DXVector3(0,0,-5),
                       D3DXVector3(0,0,0),
                       D3DXVector3(0,1,0));
    device.SetTransform(D3DTS_VIEW, matView);
    
    D3DXMatrixPerspectiveFovLH(matProj, PI/4, 1, 1, 100);
    device.SetTransform(D3DTS_PROJECTION, matProj);
    ...
    D3DXMatrixRotationY(matWorld, angle);
    device.SetTransform(D3DTS_WORLD, matWorld);
    device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    Ниже приведен пример нескольких кадров из анимации.

    Библиотека Direct3D располагает механизмом отсечения граней. Идея заключается в том, что можно указать порядок обхода вершин треугольника (по часовой или против часовой стрелки) и отключить вывод тех граней, которые перечислены, например, по часовой стрелке. По умолчанию механизм отсечения граней отключен, т.е. воспроизводятся обе стороны примитива. Программно отключение "задних" сторон примитива реализуется через вызов метода SetRenderState(D3DRS_CULLMODE, <значение>) интерфейса IDirect3DDevice9. В качестве второго параметра может выступать одна из трех констант: D3DCULL_NONE – механизм отбраковки граней выключен;

    D3DCULL_CW – отбраковываются грани, вершины которых перечислены по часовой стрелке;

    D3DCULL_CCW – отбраковываются грани, вершины которых перечислены против часовой стрелки.

    Так, на представленном ниже рисунке грань (треугольник) задается перечислением своих вершин: v0, v1, v2 – по часовой стрелке; v0, v2, v1 – против часовой стрелки.

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