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


Буфер трафарета


Библиотека Direct3D располагает средствами работы с так называемым буфером трафарета. Буфер трафарета представляет собой двумерный массив с размерами как у буфера кадра и z-буфера, причем пиксель с координатами (x,y) в буфере трафарета будет соответствовать пикселю с такими же координатами в буфере кадра. Буфер трафарета работает как маска (трафарет), позволяя нам блокировать вывод некоторых пикселей на экран по заданному правилу. Принцип работы с буфером трафарета – это, как правило, двухпроходный алгоритм. Сначала мы разбиваем область вывода на зоны и каждой зоне присваиваем номер, при этом ничего не выводится на экран. Затем на основании некоторого установленного правила, мы производим непосредственный вывод сцены на экран, причем одни зоны могут быть выведены, другие нет.

По умолчанию тест трафарета выключен (заблокирован). Чтобы его включить необходимо установить значение переменной D3DRS_STENCILENABLE в true. Программно это выглядит следующим образом:

C++

device->SetRenderState( D3DRS_STENCILENABLE, true ); // включение device->SetRenderState( D3DRS_STENCILENABLE, false ); // выключение

Pascal

device.SetRenderState( D3DRS_STENCILENABLE, 1 ); // включение device.SetRenderState( D3DRS_STENCILENABLE, 0 ); // выключение

Очистка буфера трафарета осуществляется с помощью вызова метода Clear интерфейса IDirect3DDevice9, который использовался нами и для очистки буфера кадра.

C++

device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0 );

Pascal

device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0, 0 );

Добавленная константа (D3DCLEAR_STENCIL) в третий аргумент метода указывает, что мы собираемся очищать еще и буфер трафарета. А последний параметр метода (в нашем случае он ноль) определяет значение, которым будет заполнен буфер трафарета.

В силу того, что буфер трафарета может работать только совместно с буфером глубины, нам придется сказать несколько слов и о нем. Буфер глубины или z-буфер – это вспомогательный "экран" необходимый для удаления невидимых наблюдателю граней и поверхностей.
Буфер глубины представляет собой двумерный массив, хранящий z-координаты каждого пикселя. Программно буфер глубины может быть инициализирован с помощью заполнения двух полей структуры 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; …
Установка поля EnableAutoDepthStencil в значение true разрешает работы с буферами глубины и трафарета. Поле AutoDepthStencilFormat задает их формат. В приведенном выше примере задается формат только для буфера глубины, который имеет 16-ти битный формат памяти.

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

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

D3DFMT_D24S8буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 8-ю битами на пиксель.
D3DFMT_D15S1буфер глубины определяется 15 битами на пиксель; трафаретный буфер задан одним битом на пиксель.
D3DFMT_D24X4S4буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 4-мя битами на пиксель и 4 бита не используются
Следует отметить, что не все видеокарты могут поддерживать 8-ми битный буфер трафарета.

Как мы поняли, трафаретный буфер позволяет блокировать вывод некоторых пикселей и регионов в буфере кадра. Это достигается с помощью так называемого теста трафарета, который задает функцию сравнения значения находящего в буфере трафарета (value) с некоторым заданным (ref). Эта идея может быть описана следующим псевдоматематическим выражением:

(ref & mask) ОперацияСравнения (value & mask),





где символ "&" означает побитовую операцию AND,

mask – некоторая заданная маска сравнения, которая может быть использована для того чтобы скрыть (выключить) некоторые биты одновременно в двух параметрах: value и ref (по умолчанию mask = 0xffffffff).

Изменить значение маски трафарета можно следующим образом:

C++device->SetRenderState( D3DRS_STENCILMASK, 0xff00ffff );
Pascaldevice.SetRenderState( D3DRS_STENCILMASK, $ff00ffff );
Значение, задаваемое параметром ref по умолчанию равно нулю. Изменить его можно с помощью следующих строк:

C++device->SetRenderState( D3DRS_STENCILREF, 1 );
Pascaldevice.SetRenderState( D3DRS_STENCILREF, 1 );
Тест трафарета сравнивает два значения (ref & mask) и (value & mask), и если результат сравнения выдает истину, то тест считается успешно пройденным. В качестве операции сравнения могут выступать следующие 8 предопределенных констант:

КонстантаТест трафарета завершается успешно в случае …
D3DCMP_NEVERНикогда
D3DCMP_LESSЕсли (ref & mask) < (value & mask)
D3DCMP_EQUALЕсли (ref & mask) = (value & mask)
D3DCMP_LESSEQUALЕсли (ref & mask) <= (value & mask)
D3DCMP_GREATERЕсли (ref & mask) > (value & mask)
D3DCMP_NOTEQUALЕсли (ref & mask) <> (value & mask)
D3DCMP_GREATEREQUALЕсли (ref & mask) >= (value & mask)
D3DCMP_ALWAYSВсегда
Задать нужную операцию сравнения с помощью следующего вызова:

C++device->SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> );
Pascaldevice.SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> );
где <операция сравнения> - одна из перечисленных в таблице констант.

Если тест трафарета завершился неудачно, то для данных пикселей происходит блокировка записи в буфер кадра. Если же тест трафарета проходит успешно, то пиксели записываются в буфер кадра.

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

  1. если тест трафарета завершился неудачно;
  2. если тест трафарета прошел успешно, а тест глубины завершился отрицательно;
  3. если и тест трафарета, и тест глубины завершились успешно.




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

SetRenderState( D3DRS_STENCILFAIL, <действие> ); SetRenderState( D3DRS_STENCILZFAIL, <действие>);

SetRenderState( D3DRS_STENCILPASS, <действие>);

В качестве <действие> может выступать одна из предопределенных констант:

КонстантаДействие
D3DSTENCILOP_KEEPНе изменять значение в буфере трафарета
D3DSTENCILOP_ZEROУстановить значение в буфере трафарета в ноль
D3DSTENCILOP_REPLACEЗаменить значение в буфере трафарета на значение ref, определенное константой D3DRS_STENCILREF
D3DSTENCILOP_INCRSATУвеличить значение буфера трафарета на единицу
D3DSTENCILOP_DECRSATУменьшить значение буфера трафарета на единицу
D3DSTENCILOP_INVERTПроизвести операцию побитового инвертирования
D3DSTENCILOP_INCRУвеличить значение буфера трафарета на единицу
D3DSTENCILOP_DECRУменьшить значение буфера трафарета на единицу
Различие между константами D3DSTENCILOP_INCRSAT и D3DSTENCILOP_INCR заключается в том, что при выходе за границу допустимого диапазона значений, в первом случае результат будет приведен к максимальному значению, а во втором – нулю. Аналогично с константами D3DSTENCILOP_DECRSAT и D3DSTENCILOP_DECR: в случае выхода за левую границу интервала (ноль), результат будет приведен к нулю, а во втором случае к максимальному значению.

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



Алгоритмически шаги, достижения представленного результата можно записать так:



Шаг 1.

Очистка буфера кадра и буфера трафарета (значение для фона в буфере трафарета - ноль)

device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0);



Шаг 2.

Вывод квадрата, покрытого текстурой (тест трафарета при этом отключен)

… device->SetTexture(0, tex); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); …



Шаг 3.

3.1 Включаем (разрешаем) тест трафарета.

3.2. В качестве параметра прохождения теста трафарета установить D3DCMP_NEVER (тест никогда не проходит и, следовательно, примитив не будет записан в буфер кадра).

3.3. В качестве значения записать в буфер трафарета, например, число два.

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

3.5. Вызвать функцию отрисовки желтого треугольника (сам треугольник не будет нарисован, но буфер трафарета обновит свое содержимое)

… device->SetRenderState(D3DRS_STENCILENABLE, true); device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_NEVER); device->SetRenderState(D3DRS_STENCILREF, 0x2); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); …



Шаг 4.

4.1. В качестве параметра прохождения теста трафарета установить D3DCMP_GREATER

4.2. В качестве значения, с которым будет сравниваться содержимое буфера трафарета, установить, например, число один.

4.3. Вывести разноцветный треугольник

… device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ GREATER); device->SetRenderState(D3DRS_STENCILREF, 0x1); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 3, 1); …

<


Таким образом, содержимое буфера трафарета будет выглядеть следующим образом:



Зеленый цвет соответствует значению ноль в буфере трафарета (фон), желтый – значению два, красный – значению один (разноцветный треугольник).

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

  1. Очистка буферов глубины, кадра и трафарета;
  2. Включение теста трафарета и маскирование части экрана с помощью операций с буфером трафарета (при этом для тех пикселей, которые прошли тест трафарета успешно, в буфере кадра будет сформирован некий образ);
  3. Очистка буферов глубины и кадра;
  4. Вывод всей сцены с учетом трафаретной маски.


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