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

Построение отрезков. Спрайты и вывод текста и сцен в текстуру

Показывать лекцию целиком

Построение отрезков

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

C++ ID3DXLine *line = NULL;
Pascal var line: ID3DXLine;

Создание объекта "линия" осуществляется с помощью вызова функции D3DXCreateLine, которая имеет два параметра: первый – указатель на устройство вывода, второй адрес переменной, в которую запишется результат.

C++ D3DXCreateLine(device, line);
Pascal D3DXCreateLine(device, line);

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

C++ D3DXVECTOR2 points[] = { (100.0f, 100.0f), (200.0f, 200.0f) };
Pascal var points: array[0..1] of TD3DXVector2 = ( (x:100; y:100), (x:200; y:200) );

Тип D3DXVECTOR2 представляет собой запись из двух вещественных полей, описывающих точку (вектор) в двумерном пространстве.

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

C++ line->Draw(points, 2, D3DCOLOR_XRGB(255,255,0));
Pascal line.Draw(@points, 2, D3DCOLOR_XRGB(255,255,0));

Принято вызов Draw обрамлять вызовами методов Begin и End до и после соответственно.

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

C++
line->Begin();
line->Draw(…);
line->End();
Pascal
line._Begin;
line.Draw(…);
line._End;

Наличие лидирующего символа подчеркивание у методов Begin и End в синтаксисе Паскаля обусловлено тем, что они являются ключевыми словами в данном языке.

Для установки нужной ширины рисуемой линии интерфейс ID3DXLine обладает методом SetWidth(), в качестве параметра которому передается значение ширины линии в пикселях. Например, вывод отрезка прямой линии толщиной 5 пикселей осуществляется с помощью такого кода:

C++
…
line->SetWidth(5.0f);
line->Draw(points, 2, D3DCOLOR_XRGB(255,255,0));
Pascal
…
line.SetWidth(5);
line.Draw(@points, 2, D3DCOLOR_XRGB(255,255,0));

Ниже приведены примеры вывода отрезков с шириной.

line->SetWidth(1.0f); line->SetWidth(3.0f);
line->SetWidth(5.0f); line->SetWidth(10.0f);

Для устранения лестничного эффекта (алиасинга), возникающего при растеризации отрезков, расположенных под углом к горизонту, можно воспользоваться методом SetAntialias интерфейса ID3DXLine, который имеет один параметр булевского типа. Значение true указывает на включение режима антиалиасинга, false – выключение режима. Ниже приведен пример построения отрезка прямой с включенным режимом антиалиасинга и без него.

line->SetAntialias(false);
line->SetAntialias(true);

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

N=4
N=5
N=7
N=10
N=20

Спрайты

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

Работу со спрайтами можно производить через вызовы методов интерфейса ID3DXSprite.

C++ LPD3DXSPRITE sprite = NULL;
Pascal var sprite: ID3DXSprite;

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

C++ D3DXCreateSprite(device, sprite);
Pascal D3DXCreateSprite(device, sprite);

Для отображения спрайта, как правило, прибегают к такому коду:

sprite->Begin(...)
sprite->Draw(...)
sprite->End()

Сам вывод спрайта сопряжен с взаимодействием с текстурой. Само изображение спрайта должно храниться в объекте LPDIRECT3DTEXTURE9 (загрузка изображения осуществляется через функцию D3DXCreateTextureFromFile ), а вывод его производится через вызов метода Draw интерфейса ID3DXSprite. Данный метод содержит 5 параметров: первый – указатель на текстуру, в которой хранится изображение спрайта, второй определяет прямоугольник вырезки на текстуре (если значение равно NULL, то выводится вся текстура), третий определяет точку, вокруг которой может быть повернут спрайт (если значение NULL, то поворот будет производиться вокруг левого верхнего угла), четвертый параметр задает смещение спрайта относительно верхнего левого угла в экранных координатах, и пятый параметр определяет значение цвета, на который будет умножаться каждый пиксель спрайта (значение 0xFFFFFFFF позволяет оставить значение текселя без изменений).

Простейший пример вывода спрайта может выглядеть следующим образом:

C++
// создание и загрузка спрайта
D3DXCreateTextureFromFile(device, "sprite.dds", tex);
D3DXCreateSprite(device, sprite);
…
// вывод спрайта на экран
sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->Draw(tex, NULL, NULL, NULL, D3DCOLOR_XRGB(255,255,255));
sprite->End();
Pascal
// создание и загрузка спрайта
D3DXCreateTextureFromFile(device, 'sprite.dds', tex);
D3DXCreateSprite(device, sprite);
…
// вывод спрайта на экран
sprite._Begin(D3DXSPRITE_ALPHABLEND);
sprite.Draw(tex, nil, nil, nil, D3DCOLOR_XRGB(255,255,255));
sprite._End;

Константа D3DXSPRITE_ALPHABLEND в качестве параметра метода Draw указывает на то, что выводится спрайт с прозрачным фоном.

Для масштабирования и вращения спрайтов необходимо использовать матрицы. Интерфейс ID3DXSprite предоставляет метод SetTransform(), где в качестве параметра передается матрица преобразования. Чтобы определить матрицу преобразования можно воспользоваться функцией библиотеки Direct3D – D3DXMatrixAffineTransformation2D(). Данная функция имеет пять параметров: первый определяет матрицу, в которую будет помещен результат, второй параметр определяет коэффициент масштабирования, третий задает точку вращения, четвертый – угол поворота в радианах, пятый параметр определяет вектор смещения спрайта. Следует отметить, что вначале производится операция масштабирования, затем поворот и последний шаг смещение спрайта. Ниже приведен пример вывода спрайт, который увеличен в размерах в два раза по каждой оси, повернут вокруг своего центра на угол 30 градусов и смещен на вектор (150, 100). Пусть размеры спрайта будут 256х256 пикселей.

C++
// объявление переменных 
D3DXVECTOR2 rot = D3DXVECTOR2(128.0f, 128.0f);
D3DXVECTOR2 trans = D3DXVECTOR2(150.0f, 100.0f);
D3DXMATRIX mat;

// функция вывода спрата
D3DXMatrixAffineTransformation2D(mat, 2.0f, rot, 
  30.0f*pi/180.0f, trans);

Sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->SetTransform(mat);
sprite->Draw(tex, NULL, NULL, NULL, D3DCOLOR_XRGB(255,255,255));
sprite->End();
Pascal
// объявление переменных 
var
   rot, trans,scale: TD3DXVector2;
   mat: TD3DXMatrix;

// функция вывода спрата
rot:=D3DXVector2(128,128);
trans:=D3DXVector2(150,100);
D3DXMatrixAffineTransformation2D(mat, 2, @rot, 30*pi/180, @trans);

sprite._Begin(D3DXSPRITE_ALPHABLEND);
sprite.SetTransform(mat);
sprite.Draw(tex, nil, nil, nil, D3DCOLOR_XRGB(255,255,255));
sprite._End;

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

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

В данном примере каждый кадр имеет размеры 64х64 пикселей. Для того чтобы отобразить кадр с номером i, i=1,…,30, необходимо на исходной текстуре вырезать прямоугольник размерами 64х64 пикселей с координатами левого верхнего угла X = ((i-1) mod 5)*64, Y = ((i-1) div 5)*64, где mod – операция "остаток от отделения", а div – операция "деление без остатка". Так, например, чтобы вывести кадр номер 14, нужно вырезать прямоугольник размером 64х64 с координатами левого верхнего угла (192, 128). Программно вывод нужного кадра ( frame ) можно реализовать следующим образом:

C++
RECT r;
int x0,y0;
…
x0 = ((frame-1) % 5)*64;
y0 = ((frame-1) / 5)*64;
r.left = x0;
r.top = y0;
r.right = x0+63;
r.bottom = y0+63;
sprite->Begin(D3DXSPRITE_ALPHABLEND);
sprite->Draw(tex, r, NULL, NULL, D3DCOLOR_XRGB(255,255,255));
sprite->End();
Pascal
var 
   r: TRect;
   x0,y0: Integer;
…
x0:=((frame-1) mod 5)*64;
y0:=((frame-1) div 5)*64;
r:=Rect(x0,y0,x0+63,y0+63);
sprite._Begin(D3DXSPRITE_ALPHABLEND);
sprite.Draw(tex, @r, nil, nil, D3DCOLOR_XRGB(255,255,255));
sprite._End;

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

Вывод текста

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

  • Создание текстуры, содержащей все буквы алфавита и цифры;
  • Определение для каждого символа в текстуре его положения, например, текстурные координаты;
  • Когда было нужно выводить текст, создается прямоугольник, содержащий нужный символ;
  • Устанавливаются нужные текстурные координаты, и производится вывод;
  • Эти шаги повторяются для каждого выводимого символа.
  • Библиотека Direct3D предоставляет более простые возможности по отображению текста на экране. Как обычно, вначале мы должны объявить переменные интерфейсного типа.

    C++ LPD3DXFONT font;
    Pascal var font: ID3DXFont;

    Следующий шаг – это создание самого шрифта с помощью функции D3DXCreateFont(). Данная функция имеет довольно много параметров создаваемого шрифта, но мы рассмотрим только самые необходимые. Первый параметр представляет собой ссылку на устройство вывода; второй параметр задает высоту букв шрифта (размер); третий параметр определяет ширину символов; четвертый параметр определяет, будет ли шрифт полужирным или обычным (это реализуется через предопределенные константы FW_NORMAL и FW_BOLD ); пятый аргумент определяет количество мип уровней для символа (как правило, этот параметр равен нулю); шестой параметр задает, будет ли шрифт курсивный через булевскую переменную; седьмой – задает используемый набор символов (реализуется через константы, например: ANSI_CHARSET, DEFAULT_CHARSET, SYMBOL_CHARSET, RUSSIAN_CHARSET ); восьмой параметр определяет, как операционная система будет выравнивать (подгонять) требуемый размер шрифта с реальным (как правило, используют константу OUT_DEFAULT_PRECIS ); девятый параметр имеет смысл для растровых шрифтов и задает качество интерполяции (по умолчанию используют константу DEFAULT_QUALITY ); десятый параметр определяет расстояние между символами в строке (используют константу DEFAULT_PITCH ); одиннадцатый параметр определяет название шрифта, например Arial, Times New Roman, Courier New; и двенадцатый параметр – переменная (указатель), в которую будет возвращен результат вызова. Ниже приведен пример создания полужирного шрифта высотой 72 пикселей класса Times New Roman.

    C++
    D3DXCreateFont(device, 72, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, 
                OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,  
                DEFAULT_PITCH, TEXT("Times New Roman"), font);
    Pascal
    D3DXCreateFont(device, 72,0, FW_BOLD, 0, false, DEFAULT_CHARSET, 
                OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 
                DEFAULT_PITCH , 'Times New Roman', font);

    Следующий шаг – это непосредственный вывод текста на экран. Реализуется он через вызов метода DrawText() интерфейса ID3DXFont. Метод имеет шесть параметров: первый параметр представляет собой указатель на объект ID3DXSprite (значение NULL означает, что библиотека Direct3D будет самостоятельно использовать механизм спрайтов для вывода текста); второй параметр – строка вывода; третий параметр определяет количество символов в строке (значение -1 означает, что библиотека сама подсчитает длину строки); четвертый параметр задает прямоугольную область вывода текста; пятый параметр представляет собой набор флагов, с помощью которых можно разместить выводимый текст в нужных местах (слева, справа, сверху и т.д.); шестой параметр задает цвет, которым будет выведен текст. Ниже приведен пример вывода текста, который выравнивается по правой стороне прямоугольника размерами 400х300 пикселей.

    C++
    RECT r;
    r.left=0;
    r.top=10;
    r.right=400;
    r.bottom=300;
    … 
    font->DrawText(NULL, "Тверь", -1, r, DT_RIGHT, 
      D3DCOLOR_XRGB(255,0,0));
    Pascal
    var
       r: TRect;
    …
    r:=Rect(0,0,400,300);
    font.DrawTextA(nil, 'Тверь', -1, @r, DT_RIGHT, 
      D3DCOLOR_XRGB(255,0,0));

    Ниже приведен пример вывода текста на поверхность, покрытую текстурой.

    Вывод сцены в текстуру

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

    C++
    LPDIRECT3DTEXTURE9 RenderTexture = NULL;
    LPDIRECT3DSURFACE9 RenderSurface = NULL;
    Pascal
    var
      RenderTexture: IDirect3DTexture9;
      RenderSurface: IDirect3DSurface9;

    Следующий шаг заключается в создании текстуры. Это можно проделать, воспользовавшись, к примеру, функцией D3DXCreateTexture(). Первый аргумент функции это ссылка на устройство вывода; второй и третий параметры задают ширину и высоту создаваемой текстуры соответственно; четвертый аргумент определяет количество мип- уровней (в нашем случае мип-уровни не используются, поэтому передаем ноль); пятый параметр – флаги использования (в нашем случае необходимо передать константу D3DUSAGE_RENDERTARGET, указывающую на то, что текстура будет использоваться как поверхность вывода); шестой аргумент задает пиксельный формат текстуры (здесь, как правило, наиболее часто используются константы D3DFMT_R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5 и др.); седьмой параметр – это флаг месторасположения (хранения) текстуры (здесь передаем значение по умолчанию); и последний восьмой аргумент функции – это имя переменной, в которую будет возвращен результат. Ниже приводится пример создания текстуры размером 512х512 пикселей.

    C++
    D3DXCreateTexture(device, 512, 512, 0, D3DUSAGE_RENDERTARGET, 
               D3DFMT_R8G8B8, D3DPOOL_DEFAULT, RenderTexture);
    Pascal
    D3DXCreateTexture(device, 512, 512, 0, D3DUSAGE_RENDERTARGET, 
               D3DFMT_R8G8B8, D3DPOOL_DEFAULT, RenderTexture);

    Всевозможные операции с текстурой библиотека Direct3D производит через поверхности. Для того, чтобы получить базовую поверхность любой текстуры, нужно вызвать метод GetSurfaceLevel() интерфейса IDirect3DTexture9. Метод имеет два параметра: первый – это номер текстурного уровня (так как у нас уровень всего один, то передаем ноль), а второй – указатель переменную, в которой будет храниться поверхность.

    C++ RenderTexture->GetSurfaceLevel(0, RenderSurface);
    Pascal RenderTexture.GetSurfaceLevel(0, RenderSurface);

    И последний шаг в данной методике – это указание поверхности вывода. Этот шаг реализуется через вызов метода SetRenderTarget() интерфейса IDirect3DDevice9. Этот метод имеет два параметра: первый – это индекс поверхности вывода (так как у нас поверхность одна, то передаем нулевое значение); второй аргумент – поверхность вывода.

    C++ device->SetRenderTarget(0, RenderSurface);
    Pascal device.SetRenderTarget(0, RenderSurface);

    Как правило, подобный вызов производится непосредственно в процедуре вывода примитивов перед вызовом метода Clear(). Вывод на поверхность сопряженную с текстурой ничем не отличается от обычного способа. Единственное отличие заключается в том, что результат (вся сцена) будет "воспроизводиться" в текстуре. Для того, чтобы восстановить вывод сцены обратно в буфер отображения ( back buffer ), нужно вначале программы сохранить указатель на этот буфер в некоторую переменную.

    C++
    LPDIRECT3DSURFACE9 BackBuffer = NULL;
    …
    device->GetRenderTarget(0, BackBuffer);
    Pascal
    var
       BackBuffer: IDirect3DSurface9;
    …
    device.GetRenderTarget(0, BackBuffer);

    Затем необходимо вызвать метод SetRenderTarget(), и в качестве второго параметра передать ссылку на сохраненную поверхность вывода ( back buffer ).

    C++ device->SetRenderTarget(0, BackBuffer);
    Pascal device.SetRenderTarget(0, BackBuffer);

    Поверхность вывода, ассоциированную с текстурой можно в любой момент сохранить в файл на диске. Для этого можно воспользоваться функцией D3DXSaveTextureToFile(), которая имеет четыре параметра. Первый аргумент – это указатель на строку, содержащую имя сохраняемого файла; второй параметр определяет формат графического файла (задается одной из следующих констант: D3DXIFF_BMP, D3DXIFF_JPG, D3DXIFF_TGA, D3DXIFF_PNG, D3DXIFF_DDS, D3DXIFF_PPM, D3DXIFF_DIB, D3DXIFF_HDR, D3DXIFF_PFM ); третий аргумент – это ссылка на интерфейс IDirect3DBaseTexture9, содержащий нужную текстуру; и четвертый параметр представляет собой ссылку на структуру, определяющую палитру из 256 цветов. Ниже приведен пример сохранения поверхности вывода, ассоциированной с текстурой в файл на диске.

    C++ D3DXSaveTextureToFile("scene.bmp", D3DXIFF_BMP, RenderTexture, NULL);
    Pascal D3DXSaveTextureToFile('scene.bmp', D3DXIFF_BMP, RenderTexture, nil);
    Вернуться к учебному плану