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

         

Цвета вершин


Цвета примитивов задаются путем указания цветов образующих их вершин. Следовательно, необходимо добавить к структуре данных вершины члены для хранения цветовых компонентов. Обратите внимание, что здесь нельзя использовать тип D3DCOLORVALUE, поскольку Direct3D ожидает описания цвета вершины в виде 32-разрядного значения. (Как ни странно, при работе с вершинными шейдерами мы должны использовать для задания цвета вершин четырехмерные цветовые векторы, и, следовательно, указывать 128-разрядное значение цвета, но сейчас не будем говорить об этом. Вершинные шейдеры рассматриваются в главе 17.)

struct ColorVertex { float _x, _y, _z; D3DCOLOR _color; static const DWORD FVF; } const DWORD ColorVertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;



Цвет


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

Цели

Узнать, как в Direct3D задается цвет.

Изучить способы закрашивания треугольников.



Цвета задаются путем указания их


Цвета задаются путем указания их красной, зеленой и синей составляющих. Смешивание трех базовых цветов различной интенсивности позволяет задавать миллионы различных цветов. В Direct3D для опиисания цветов в коде могут использоваться типы D3DCOLOR, D3DCOLORVALUE или D3DXCOLOR.
Иногда мы будем рассматривать цвет как четырехмерный вектор (r, g, b, a). Цветовые векторы могут складываться, вычитаться и масштабироваться как обычные. В то же время операции скалярного или векторного произведения для цветовых векторов не имеют смысла. Имеет смысл операция перемножения компонент двух цветовых векторов, которая обозначается знаком
и определена следующим образом: (c1, c2, c3, c4) 
 (k1, k2, k3, k4) = (c1k1, c2k2, c3k3, c4k4).
Мы задаем цвета вершин, а Direct3D во время растеризации определяет цвет каждого пиксела грани с учетом выбранного режима затенения.
При равномерном затенении все пикселы примитива окрашиваются в цвет его первой вершины. При затенении по методу Гуро цвет каждого пикселя примитива вычисляется путем линейной интерполяции значений цветов его вершин.

Представление цвета




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

Для хранения информации о цвете мы будем использовать две различные структуры. Первая— это тип D3DCOLOR, который представляется значением DWORD и является 32-разрядным. Отдельные разряды в типе D3DCOLOR делятся на четыре 8-разрядных секции, каждая из которых хранит интенсивность отдельного компонента. Распределение значений показано на Рисунок  4.1.



Пример приложения: цветные треугольники


Пример приложения из этой главы отображает закрашенные треугольники: для одного из них используется равномерное затенение, а для другого — затенение по методу Гуро. Результат визуализации показан на Рисунок  4.2. Сперва мы добавляем несколько глобальных переменных:

D3DXMATRIX World; IDirect3DVertexBuffer9* Triangle = 0;

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

Метод Setup создает буфер вершин и заполняет его данными вершин, для каждой из которых также указывается цвет. Первая вершина треугольника становится красной, вторая — зеленой, а третья — синей. Потом для данного примера мы запрещаем освещение. Обратите внимание, что в данном примере используется новая структура данных вершин ColorVertex, которая была описана в разделе 4.2.

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

// Заполнение буфера данными вершин треугольника ColorVertex* v; Triangle->Lock(0, 0, (void**)&v, 0);

v[0] = ColorVertex(-1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0)); v[1] = ColorVertex( 0.0f, 1.0f, 2.0f, D3DCOLOR_XRGB( 0, 255, 0)); v[2] = ColorVertex( 1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB( 0, 0, 255));

Triangle->Unlock();

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

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

return true; }

Затем функция Display дважды рисует объект Triangle в двух различных местах и с различными режимами затенения.
Позиция каждого треугольника задается матрицей мирового преобразования World.
bool Display(float timeDelta) { if(Device) { Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene();
Device->SetFVF(ColorVertex::FVF); Device->SetStreamSource(0, Triangle, 0, sizeof(ColorVertex));
// Рисуем левый треугольник с равномерной заливкой D3DXMatrixTranslation(&World, -1.25f, 0.0f, 0.0f); Device->SetTransform(D3DTS_WORLD, &World);
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// Рисуем правый треугольник с заливкой Гуро D3DXMatrixTranslation(&World, 1.25f, 0.0f, 0.0f); Device->SetTransform(D3DTS_WORLD, &World);
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }

-Разрядное представление цвета


Рисунок 4.1. 32-разрядное представление цвета, где для каждого основного компоненнта (красного, зеленого и синего) выделено по одному байту. Четвертый байт отведен для альфа-канала


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

ПРИМЕЧАНИЕ

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

Если задавать каждый компонент цвета и затем помещать его в требуемую позицию значения типа D3DCOLOR, потребуется выполнить несколько поразрядных операций. Direct3D предоставляет макрос с именем D3DCOLOR_ARGB, который выполнит все эти действия за нас. У макроса есть по одному параметру для каждой цветовой составляющей и один параметр для альфа-канала. Значение каждого параметра может быть от 0 до 255, а использование макроса выглядит так:

D3DCOLOR brightRed = D3DCOLOR_ARGB(255, 255, 0, 0); D3DCOLOR someColor = D3DCOLOR_ARGB(255, 144, 87, 201);

Альтернативным способом является использование макроса D3DCOLOR_XRGB, который работает почти так же, но не получает значение для альфа-канала; вместо этого значение альфа-канала всегда устанавливается равным 0xff (255).

#define D3DCOLOR_XRGB(r,g,b) D3DCOLOR_ARGB(0xff,r,g,b)

Вторым способом хранения данных цвета в Direct3D является структура D3DCOLORVALUE. В этой структуре для задания интенсивности каждого компонента цвета применяются значения с плавающей точкой. Доступный диапазон значений от 0 до 1; 0 соответствует отсутствию даного цвета, а 1 — его максимальной интенсивности.

typedef struct _D3DCOLORVALUE { float r; // красная составляющая, диапазон 0.0-1.0 float g; // зеленая составляющая, диапазон 0.0-1.0 float b; // синяя составляющая, диапазон 0.0-1.0 float a; // альфа-составляющая, диапазон 0.0-1.0 } D3DCOLORVALUE;

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

typedef struct D3DXCOLOR { #ifdef __cplusplus public: D3DXCOLOR() {} D3DXCOLOR(DWORD argb); D3DXCOLOR(CONST FLOAT *); D3DXCOLOR(CONST D3DXFLOAT16 *); D3DXCOLOR(CONST D3DCOLORVALUE&); D3DXCOLOR(FLOAT r, FLOAT g, FLOAT b, FLOAT a);

// приведение типов operator DWORD () const;

operator FLOAT* (); operator CONST FLOAT* () const; operator D3DCOLORVALUE* (); operator CONST D3DCOLORVALUE* () const;

operator D3DCOLORVALUE& (); operator CONST D3DCOLORVALUE& () const;

// операторы присваивания D3DXCOLOR& operator += (CONST D3DXCOLOR&); D3DXCOLOR& operator -= (CONST D3DXCOLOR&); D3DXCOLOR& operator *= (FLOAT); D3DXCOLOR& operator /= (FLOAT);

// унарные операторы D3DXCOLOR operator + () const; D3DXCOLOR operator - () const;

// бинарные операторы D3DXCOLOR operator + (CONST D3DXCOLOR&) const; D3DXCOLOR operator - (CONST D3DXCOLOR&) const; D3DXCOLOR operator * (FLOAT) const; D3DXCOLOR operator / (FLOAT) const;

friend D3DXCOLOR operator * (FLOAT, CONST D3DXCOLOR&);

BOOL operator == (CONST D3DXCOLOR&) const; BOOL operator != (CONST D3DXCOLOR&) const;

#endif //__cplusplus FLOAT r, g, b, a; } D3DXCOLOR, *LPD3DXCOLOR;

ПРИМЕЧАНИЕ

Обратите внимание, что в структурах D3DCOLORVALUE и D3DXCOLOR имеется по четыре значения с плавающей точкой. Это позволяет предствить цвет как четырехмерный вектор (r, g, b, a). Цветовые векторы могут складываться, вычитаться и масштабироваться как обычные. В то же время скалярное и векторное произведение цветовых векторов не имеют смысла. Однако, перемножение отдельных компонент для цветовых векторов имеет смысл. Так что оператор умножения цвета на цвет в классе D3DXCOLOR перемножает отдельные компоненты. Символ

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

(c1, c2, c3, c4) 
 (k1, k2, k3, k4) = (c1k1, c2k2, c3k3, c4k4) Мы дополним наш файл d3dUtility.h следующими глобальными цветовыми константами:

namespace d3d { . . . const D3DXCOLOR WHITE(D3DCOLOR_XRGB(255, 255, 255)); const D3DXCOLOR BLACK(D3DCOLOR_XRGB( 0, 0, 0)); const D3DXCOLOR RED(D3DCOLOR_XRGB(255, 0, 0)); const D3DXCOLOR GREEN(D3DCOLOR_XRGB( 0, 255, 0)); const D3DXCOLOR BLUE(D3DCOLOR_XRGB( 0, 0, 255)); const D3DXCOLOR YELLOW(D3DCOLOR_XRGB(255, 255, 0)); const D3DXCOLOR CYAN(D3DCOLOR_XRGB( 0, 255, 255)); const D3DXCOLOR MAGENTA(D3DCOLOR_XRGB(255, 0, 255)); }


Слева равномерное затенение треугольника



Рисунок 4.2. Слева равномерное затенение треугольника красным цветом. Справа треугольник с вершинами красного, зеленого и синего цвета, затенение которого выполнялось по методу Гуро; обратите внимание на интерполяцию цветов


Подобно многим другим вещам в Direct3D, режим затенения устанавливается через механизм режимов визуализации Direct3D.

// включение равномерной заливки Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);

// включение заливки по методу Гуро Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);



Затенение


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

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

ColorVertex t[3]; t[0]._color = D3DCOLOR_XRGB(255, 0, 0); t[1]._color = D3DCOLOR_XRGB(0, 255, 0); t[2]._color = D3DCOLOR_XRGB(0, 0, 255);

При равномерном затенении объекты выглядят угловатыми, поскольку нет плавных переходов от одного цвета к другому. Более качественным вариантом является затенение по алгоритму Гуро (также называемое гладким затенением). При затенении по методу Гуро цвета каждой точки определяются путем линейной интерполяции цветов вершин примитива. На Рисунок  4.2 показаны два треугольника: один закрашен с использованием равномерного затенения, а другой — с использованием затенения по методу Гуро.